Browse Source

feat(auditing): 提供有关用户的审计日志记录

pull/578/head
cKey 4 years ago
parent
commit
12a8a52ec0
  1. 2
      aspnet-core/modules/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs
  2. 371
      aspnet-core/modules/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchEntityChangeStore.cs
  3. 15
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/EntityChangeGetByPagedDto.cs
  4. 7
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/EntityChangeGetWithUsernameDto.cs
  5. 7
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/EntityChangeWithUsernameDto.cs
  6. 17
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/IEntityChangesAppService.cs
  7. 1
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application/LINGYUN/Abp/Auditing/AbpAuditingMapperProfile.cs
  8. 62
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application/LINGYUN/Abp/Auditing/AuditLogs/EntityChangesAppService.cs
  9. 50
      aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.HttpApi/LINGYUN/Abp/Auditing/AuditLogs/EntityChangesController.cs
  10. 3
      aspnet-core/modules/elasticsearch/LINGYUN.Abp.Elasticsearch/LINGYUN/Abp/Elasticsearch/AbpElasticsearchOptions.cs

2
aspnet-core/modules/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchAuditLogManager.cs

@ -254,7 +254,7 @@ namespace LINGYUN.Abp.AuditLogging.Elasticsearch
}
if (!url.IsNullOrWhiteSpace())
{
querys.Add((log) => log.Match((q) => q.Field(GetField(nameof(AuditLog.Url))).Query(url)));
querys.Add((log) => log.Wildcard((q) => q.Field(GetField(nameof(AuditLog.Url))).Value($"*{url}*")));
}
if (userId.HasValue)
{

371
aspnet-core/modules/auditing/LINGYUN.Abp.AuditLogging.Elasticsearch/LINGYUN/Abp/AuditLogging/Elasticsearch/ElasticsearchEntityChangeStore.cs

@ -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();
}
}

15
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/EntityChangeGetByPagedDto.cs

@ -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; }
}

7
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/EntityChangeGetWithUsernameDto.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.Auditing.AuditLogs;
public class EntityChangeGetWithUsernameDto
{
public string EntityId { get; set; }
public string EntityTypeFullName { get; set; }
}

7
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/EntityChangeWithUsernameDto.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.Auditing.AuditLogs;
public class EntityChangeWithUsernameDto
{
public EntityChangeDto EntityChange { get; set; }
public string UserName { get; set; }
}

17
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application.Contracts/LINGYUN/Abp/Auditing/AuditLogs/IEntityChangesAppService.cs

@ -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);
}

1
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application/LINGYUN/Abp/Auditing/AbpAuditingMapperProfile.cs

@ -14,6 +14,7 @@ namespace LINGYUN.Abp.Auditing
CreateMap<AuditLogAction, AuditLogActionDto>()
.MapExtraProperties();
CreateMap<EntityPropertyChange, EntityPropertyChangeDto>();
CreateMap<EntityChangeWithUsername, EntityChangeWithUsernameDto>();
CreateMap<EntityChange, EntityChangeDto>()
.MapExtraProperties();
CreateMap<AuditLog, AuditLogDto>()

62
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.Application/LINGYUN/Abp/Auditing/AuditLogs/EntityChangesAppService.cs

@ -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));
}
}

50
aspnet-core/modules/auditing/LINGYUN.Abp.Auditing.HttpApi/LINGYUN/Abp/Auditing/AuditLogs/EntityChangesController.cs

@ -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);
}
}

3
aspnet-core/modules/elasticsearch/LINGYUN.Abp.Elasticsearch/LINGYUN/Abp/Elasticsearch/AbpElasticsearchOptions.cs

@ -15,6 +15,7 @@ namespace LINGYUN.Abp.Elasticsearch
/// 默认:false
/// </summary>
public bool FieldCamelCase { get; set; }
public bool DisableDirectStreaming { get; set; }
public string NodeUris { get; set; }
public int ConnectionLimit { get; set; }
public string UserName { get; set; }
@ -61,6 +62,8 @@ namespace LINGYUN.Abp.Elasticsearch
configuration.BasicAuthentication(UserName, Password);
}
configuration.DisableDirectStreaming(DisableDirectStreaming);
return configuration;
}
}

Loading…
Cancel
Save