10 changed files with 534 additions and 1 deletions
@ -0,0 +1,371 @@ |
|||
using LINGYUN.Abp.Elasticsearch; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Nest; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.AuditLogging.Elasticsearch; |
|||
|
|||
[Dependency(ReplaceServices = true)] |
|||
public class ElasticsearchEntityChangeStore : IEntityChangeStore, ITransientDependency |
|||
{ |
|||
private readonly AbpElasticsearchOptions _elasticsearchOptions; |
|||
private readonly IIndexNameNormalizer _indexNameNormalizer; |
|||
private readonly IElasticsearchClientFactory _clientFactory; |
|||
|
|||
public ILogger<ElasticsearchEntityChangeStore> Logger { protected get; set; } |
|||
|
|||
public ElasticsearchEntityChangeStore( |
|||
IIndexNameNormalizer indexNameNormalizer, |
|||
IElasticsearchClientFactory clientFactory, |
|||
IOptions<AbpElasticsearchOptions> elasticsearchOptions) |
|||
{ |
|||
_clientFactory = clientFactory; |
|||
_indexNameNormalizer = indexNameNormalizer; |
|||
_elasticsearchOptions = elasticsearchOptions.Value; |
|||
|
|||
Logger = NullLogger<ElasticsearchEntityChangeStore>.Instance; |
|||
} |
|||
|
|||
public async virtual Task<EntityChange> GetAsync( |
|||
Guid entityChangeId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var client = _clientFactory.Create(); |
|||
|
|||
var resposne = await client.SearchAsync<EntityChange>( |
|||
dsl => dsl.Index(CreateIndex()) |
|||
.Query(query => |
|||
query.Bool(bo => |
|||
bo.Must(m => |
|||
m.Nested(n => |
|||
n.InnerHits() |
|||
.Path("EntityChanges") |
|||
.Query(nq => |
|||
nq.Term(nqt => |
|||
nqt.Field(GetField(nameof(EntityChange.Id))).Value(entityChangeId))))))) |
|||
.Source(x => x.Excludes(f => f.Field("*"))) |
|||
.Sort(entity => entity.Field("EntityChanges.ChangeTime", SortOrder.Descending)) |
|||
.Size(1), |
|||
ct: cancellationToken); |
|||
|
|||
if (resposne.Shards.Successful > 0) |
|||
{ |
|||
var hits = resposne.Hits.FirstOrDefault(); |
|||
if (hits.InnerHits.Count > 0) |
|||
{ |
|||
return hits.InnerHits.First().Value.Documents<EntityChange>().FirstOrDefault(); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public async virtual Task<long> GetCountAsync( |
|||
Guid? auditLogId = null, |
|||
DateTime? startTime = null, |
|||
DateTime? endTime = null, |
|||
EntityChangeType? changeType = null, |
|||
string entityId = null, |
|||
string entityTypeFullName = null, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
await Task.CompletedTask; |
|||
return 0; |
|||
//var client = _clientFactory.Create();
|
|||
|
|||
//var querys = BuildQueryDescriptor(
|
|||
// auditLogId,
|
|||
// startTime,
|
|||
// endTime,
|
|||
// changeType,
|
|||
// entityId,
|
|||
// entityTypeFullName);
|
|||
|
|||
//Func<QueryContainerDescriptor<EntityChange>, QueryContainer> selector = q => q.MatchAll();
|
|||
//if (querys.Count > 0)
|
|||
//{
|
|||
// selector = q => q.Bool(b => b.Must(querys.ToArray()));
|
|||
//}
|
|||
|
|||
//var response = await client.CountAsync<EntityChange>(dsl =>
|
|||
// dsl.Index(CreateIndex())
|
|||
// .Query(q =>
|
|||
// q.Bool(b =>
|
|||
// b.Must(m =>
|
|||
// m.Nested(n =>
|
|||
// n.InnerHits(hit => hit.Source(s => s.ExcludeAll()))
|
|||
// .Path("EntityChanges")
|
|||
// .Query(selector)
|
|||
// )
|
|||
// )
|
|||
// )
|
|||
// ),
|
|||
// ct: cancellationToken);
|
|||
|
|||
//return response.Count;
|
|||
} |
|||
|
|||
public async virtual Task<List<EntityChange>> GetListAsync( |
|||
string sorting = null, |
|||
int maxResultCount = 50, |
|||
int skipCount = 0, |
|||
Guid? auditLogId = null, |
|||
DateTime? startTime = null, |
|||
DateTime? endTime = null, |
|||
EntityChangeType? changeType = null, |
|||
string entityId = null, |
|||
string entityTypeFullName = null, |
|||
bool includeDetails = false, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
// TODO: 需要解决Nested格式数据返回方式
|
|||
|
|||
//var client = _clientFactory.Create();
|
|||
|
|||
//var sortOrder = !sorting.IsNullOrWhiteSpace() && sorting.EndsWith("asc", StringComparison.InvariantCultureIgnoreCase)
|
|||
// ? SortOrder.Ascending : SortOrder.Descending;
|
|||
//sorting = !sorting.IsNullOrWhiteSpace()
|
|||
// ? sorting.Split()[0]
|
|||
// : nameof(EntityChange.ChangeTime);
|
|||
|
|||
//var querys = BuildQueryDescriptor(
|
|||
// auditLogId,
|
|||
// startTime,
|
|||
// endTime,
|
|||
// changeType,
|
|||
// entityId,
|
|||
// entityTypeFullName);
|
|||
|
|||
//SourceFilterDescriptor<EntityChange> SourceFilter(SourceFilterDescriptor<EntityChange> selector)
|
|||
//{
|
|||
// selector.Includes(GetEntityChangeSources());
|
|||
// if (!includeDetails)
|
|||
// {
|
|||
// selector.Excludes(field =>
|
|||
// field.Field("EntityChanges.PropertyChanges")
|
|||
// .Field("EntityChanges.ExtraProperties"));
|
|||
// }
|
|||
|
|||
// return selector;
|
|||
//}
|
|||
|
|||
//Func<QueryContainerDescriptor<EntityChange>, QueryContainer> selector = q => q.MatchAll();
|
|||
//if (querys.Count > 0)
|
|||
//{
|
|||
// selector = q => q.Bool(b => b.Must(querys.ToArray()));
|
|||
//}
|
|||
|
|||
//var response = await client.SearchAsync<EntityChange>(dsl =>
|
|||
// dsl.Index(CreateIndex())
|
|||
// .Query(q =>
|
|||
// q.Bool(b =>
|
|||
// b.Must(m =>
|
|||
// m.Nested(n =>
|
|||
// n.InnerHits(hit => hit.Source(SourceFilter))
|
|||
// .Path("EntityChanges")
|
|||
// .Query(selector)
|
|||
// )
|
|||
// )
|
|||
// )
|
|||
// )
|
|||
// .Source(x => x.Excludes(f => f.Field("*")))
|
|||
// .Sort(entity => entity.Field(GetField(sorting), sortOrder))
|
|||
// .From(skipCount)
|
|||
// .Size(maxResultCount),
|
|||
// cancellationToken);
|
|||
|
|||
//if (response.Shards.Successful > 0)
|
|||
//{
|
|||
// var hits = response.Hits.FirstOrDefault();
|
|||
// if (hits.InnerHits.Count > 0)
|
|||
// {
|
|||
// return hits.InnerHits.First().Value.Documents<EntityChange>().ToList();
|
|||
// }
|
|||
//}
|
|||
await Task.CompletedTask; |
|||
return new List<EntityChange>(); |
|||
} |
|||
|
|||
public async virtual Task<EntityChangeWithUsername> GetWithUsernameAsync( |
|||
Guid entityChangeId, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var client = _clientFactory.Create(); |
|||
|
|||
var response = await client.SearchAsync<AuditLog>( |
|||
dsl => dsl.Index(CreateIndex()) |
|||
.Query(query => |
|||
query.Bool(bo => |
|||
bo.Must(m => |
|||
m.Nested(n => |
|||
n.InnerHits() |
|||
.Path("EntityChanges") |
|||
.Query(nq => |
|||
nq.Bool(nb => |
|||
nb.Must(nm => |
|||
nm.Term(nt => |
|||
nt.Field(GetField(nameof(EntityChange.Id))).Value(entityChangeId))))))))) |
|||
.Source(selector => selector.Includes(field => |
|||
field.Field(f => f.UserName))) |
|||
.Size(1), |
|||
ct: cancellationToken); |
|||
|
|||
var auditLog = response.Documents.FirstOrDefault(); |
|||
EntityChange entityChange = null; |
|||
|
|||
if (response.Shards.Successful > 0) |
|||
{ |
|||
var hits = response.Hits.FirstOrDefault(); |
|||
if (hits.InnerHits.Count > 0) |
|||
{ |
|||
entityChange = hits.InnerHits.First().Value.Documents<EntityChange>().FirstOrDefault(); |
|||
} |
|||
} |
|||
|
|||
return new EntityChangeWithUsername() |
|||
{ |
|||
EntityChange = entityChange, |
|||
UserName = auditLog?.UserName |
|||
}; |
|||
} |
|||
|
|||
public async virtual Task<List<EntityChangeWithUsername>> GetWithUsernameAsync( |
|||
string entityId, |
|||
string entityTypeFullName, |
|||
CancellationToken cancellationToken = default) |
|||
{ |
|||
var client = _clientFactory.Create(); |
|||
|
|||
var response = await client.SearchAsync<AuditLog>( |
|||
dsl => dsl.Index(CreateIndex()) |
|||
.Query(query => |
|||
query.Bool(bo => |
|||
bo.Must(m => |
|||
m.Nested(n => |
|||
n.InnerHits() |
|||
.Path("EntityChanges") |
|||
.Query(nq => |
|||
nq.Bool(nb => |
|||
nb.Must(nm => |
|||
nm.Term(nt => |
|||
nt.Field(GetField(nameof(EntityChange.EntityId))).Value(entityId)), |
|||
nm => |
|||
nm.Term(nt => |
|||
nt.Field(GetField(nameof(EntityChange.EntityTypeFullName))).Value(entityTypeFullName)) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
.Source(selector => selector.Includes(field => |
|||
field.Field(f => f.UserName))) |
|||
.Sort(entity => entity.Field(f => f.ExecutionTime, SortOrder.Descending)), |
|||
ct: cancellationToken); |
|||
|
|||
if (response.Hits.Count > 0) |
|||
{ |
|||
return response.Hits. |
|||
Select(hit => new EntityChangeWithUsername |
|||
{ |
|||
UserName = hit.Source.UserName, |
|||
EntityChange = hit.InnerHits.Any() ? |
|||
hit.InnerHits.First().Value.Documents<EntityChange>().FirstOrDefault() |
|||
: null |
|||
}) |
|||
.ToList(); |
|||
} |
|||
|
|||
return new List<EntityChangeWithUsername>(); |
|||
} |
|||
|
|||
protected virtual List<Func<QueryContainerDescriptor<EntityChange>, QueryContainer>> BuildQueryDescriptor( |
|||
Guid? auditLogId = null, |
|||
DateTime? startTime = null, |
|||
DateTime? endTime = null, |
|||
EntityChangeType? changeType = null, |
|||
string entityId = null, |
|||
string entityTypeFullName = null) |
|||
{ |
|||
var querys = new List<Func<QueryContainerDescriptor<EntityChange>, QueryContainer>>(); |
|||
|
|||
if (auditLogId.HasValue) |
|||
{ |
|||
querys.Add(entity => entity.Term(q => q.Field(GetField(nameof(EntityChange.AuditLogId))).Value(auditLogId))); |
|||
} |
|||
if (startTime.HasValue) |
|||
{ |
|||
querys.Add(entity => entity.DateRange(q => q.Field(GetField(nameof(EntityChange.ChangeTime))).GreaterThanOrEquals(startTime))); |
|||
} |
|||
if (endTime.HasValue) |
|||
{ |
|||
querys.Add(entity => entity.DateRange(q => q.Field(GetField(nameof(EntityChange.ChangeTime))).LessThanOrEquals(endTime))); |
|||
} |
|||
if (changeType.HasValue) |
|||
{ |
|||
querys.Add(entity => entity.Term(q => q.Field(GetField(nameof(EntityChange.ChangeType))).Value(changeType))); |
|||
} |
|||
if (!entityId.IsNullOrWhiteSpace()) |
|||
{ |
|||
querys.Add(entity => entity.Term(q => q.Field(GetField(nameof(EntityChange.EntityId))).Value(entityId))); |
|||
} |
|||
if (!entityTypeFullName.IsNullOrWhiteSpace()) |
|||
{ |
|||
querys.Add(entity => entity.Wildcard(q => q.Field(GetField(nameof(EntityChange.EntityTypeFullName))).Value($"*{entityTypeFullName}*"))); |
|||
} |
|||
|
|||
return querys; |
|||
} |
|||
|
|||
protected virtual string CreateIndex() |
|||
{ |
|||
return _indexNameNormalizer.NormalizeIndex("audit-log"); |
|||
} |
|||
|
|||
protected Func<FieldsDescriptor<EntityChange>, IPromise<Fields>> GetEntityChangeSources() |
|||
{ |
|||
return field => field |
|||
.Field("EntityChanges.Id") |
|||
.Field("EntityChanges.AuditLogId") |
|||
.Field("EntityChanges.TenantId") |
|||
.Field("EntityChanges.ChangeTime") |
|||
.Field("EntityChanges.ChangeType") |
|||
.Field("EntityChanges.EntityTenantId") |
|||
.Field("EntityChanges.EntityId") |
|||
.Field("EntityChanges.EntityTypeFullName") |
|||
.Field("EntityChanges.PropertyChanges") |
|||
.Field("EntityChanges.ExtraProperties"); |
|||
} |
|||
|
|||
private readonly static IDictionary<string, string> _fieldMaps = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) |
|||
{ |
|||
{ "Id", "EntityChanges.Id.keyword" }, |
|||
{ "AuditLogId", "EntityChanges.AuditLogId.keyword" }, |
|||
{ "TenantId", "EntityChanges.TenantId.keyword" }, |
|||
{ "EntityTenantId", "EntityChanges.EntityTenantId.keyword" }, |
|||
{ "EntityId", "EntityChanges.EntityId.keyword" }, |
|||
{ "EntityTypeFullName", "EntityChanges.EntityTypeFullName.keyword" }, |
|||
{ "PropertyChanges", "EntityChanges.PropertyChanges" }, |
|||
{ "ExtraProperties", "EntityChanges.ExtraProperties" }, |
|||
{ "ChangeType", "EntityChanges.ChangeType" }, |
|||
{ "ChangeTime", "EntityChanges.ChangeTime" }, |
|||
}; |
|||
protected virtual string GetField(string field) |
|||
{ |
|||
if (_fieldMaps.TryGetValue(field, out var mapField)) |
|||
{ |
|||
return _elasticsearchOptions.FieldCamelCase ? mapField.ToCamelCase() : mapField.ToPascalCase(); |
|||
} |
|||
|
|||
return _elasticsearchOptions.FieldCamelCase ? field.ToCamelCase() : field.ToPascalCase(); |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Auditing; |
|||
|
|||
namespace LINGYUN.Abp.Auditing.AuditLogs; |
|||
|
|||
public class EntityChangeGetByPagedDto : 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; } |
|||
public string EntityTypeFullName { get; set; } |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.Auditing.AuditLogs; |
|||
|
|||
public class EntityChangeGetWithUsernameDto |
|||
{ |
|||
public string EntityId { get; set; } |
|||
public string EntityTypeFullName { get; set; } |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace LINGYUN.Abp.Auditing.AuditLogs; |
|||
public class EntityChangeWithUsernameDto |
|||
{ |
|||
public EntityChangeDto EntityChange { get; set; } |
|||
|
|||
public string UserName { get; set; } |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.Auditing.AuditLogs; |
|||
|
|||
public interface IEntityChangesAppService : IApplicationService |
|||
{ |
|||
Task<EntityChangeDto> GetAsync(Guid id); |
|||
|
|||
Task<EntityChangeWithUsernameDto> GetWithUsernameAsync(Guid id); |
|||
|
|||
Task<PagedResultDto<EntityChangeDto>> GetListAsync(EntityChangeGetByPagedDto input); |
|||
|
|||
Task<ListResultDto<EntityChangeWithUsernameDto>> GetWithUsernameAsync(EntityChangeGetWithUsernameDto input); |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using LINGYUN.Abp.Auditing.Features; |
|||
using LINGYUN.Abp.Auditing.Permissions; |
|||
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.Features; |
|||
|
|||
namespace LINGYUN.Abp.Auditing.AuditLogs; |
|||
|
|||
[Authorize(AuditingPermissionNames.AuditLog.Default)] |
|||
[RequiresFeature(AuditingFeatureNames.Logging.AuditLog)] |
|||
public class EntityChangesAppService : AuditingApplicationServiceBase, IEntityChangesAppService |
|||
{ |
|||
protected IEntityChangeStore EntityChangeStore { get; } |
|||
|
|||
public EntityChangesAppService( |
|||
IEntityChangeStore entityChangeStore) |
|||
{ |
|||
EntityChangeStore = entityChangeStore; |
|||
} |
|||
|
|||
public async virtual Task<EntityChangeDto> GetAsync(Guid id) |
|||
{ |
|||
var entityChange = await EntityChangeStore.GetAsync(id); |
|||
|
|||
return ObjectMapper.Map<EntityChange, EntityChangeDto>(entityChange); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<EntityChangeDto>> GetListAsync(EntityChangeGetByPagedDto input) |
|||
{ |
|||
var totalCount = await EntityChangeStore.GetCountAsync( |
|||
input.AuditLogId, input.StartTime, input.EndTime, |
|||
input.ChangeType, input.EntityId, input.EntityTypeFullName); |
|||
|
|||
var entityChanges = await EntityChangeStore.GetListAsync( |
|||
input.Sorting, input.MaxResultCount, input.SkipCount, |
|||
input.AuditLogId, input.StartTime, input.EndTime, |
|||
input.ChangeType, input.EntityId, input.EntityTypeFullName); |
|||
|
|||
return new PagedResultDto<EntityChangeDto>(totalCount, |
|||
ObjectMapper.Map<List<EntityChange>, List<EntityChangeDto>>(entityChanges)); |
|||
} |
|||
|
|||
public async virtual Task<EntityChangeWithUsernameDto> GetWithUsernameAsync(Guid id) |
|||
{ |
|||
var entityChangeWithUsername = await EntityChangeStore.GetWithUsernameAsync(id); |
|||
|
|||
return ObjectMapper.Map<EntityChangeWithUsername, EntityChangeWithUsernameDto>(entityChangeWithUsername); |
|||
} |
|||
|
|||
public async virtual Task<ListResultDto<EntityChangeWithUsernameDto>> GetWithUsernameAsync(EntityChangeGetWithUsernameDto input) |
|||
{ |
|||
var entityChangeWithUsernames = await EntityChangeStore.GetWithUsernameAsync( |
|||
input.EntityId, input.EntityTypeFullName); |
|||
|
|||
return new ListResultDto<EntityChangeWithUsernameDto>( |
|||
ObjectMapper.Map<List<EntityChangeWithUsername>, List<EntityChangeWithUsernameDto>>(entityChangeWithUsernames)); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace LINGYUN.Abp.Auditing.AuditLogs; |
|||
|
|||
[RemoteService(Name = AuditingRemoteServiceConsts.RemoteServiceName)] |
|||
[Area("auditing")] |
|||
[ControllerName("entity-changes")] |
|||
[Route("api/auditing/entity-changes")] |
|||
public class EntityChangesController : AbpControllerBase, IEntityChangesAppService |
|||
{ |
|||
protected IEntityChangesAppService EntityChangeAppService { get; } |
|||
|
|||
public EntityChangesController( |
|||
IEntityChangesAppService entityChangeAppService) |
|||
{ |
|||
EntityChangeAppService = entityChangeAppService; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public Task<EntityChangeDto> GetAsync(Guid id) |
|||
{ |
|||
return EntityChangeAppService.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public Task<PagedResultDto<EntityChangeDto>> GetListAsync(EntityChangeGetByPagedDto input) |
|||
{ |
|||
return EntityChangeAppService.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("with-username/{id}")] |
|||
public Task<EntityChangeWithUsernameDto> GetWithUsernameAsync(Guid id) |
|||
{ |
|||
return EntityChangeAppService.GetWithUsernameAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("with-username")] |
|||
public Task<ListResultDto<EntityChangeWithUsernameDto>> GetWithUsernameAsync(EntityChangeGetWithUsernameDto input) |
|||
{ |
|||
return EntityChangeAppService.GetWithUsernameAsync(input); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue