feat: 添加审计日志模块

main
NoahLan 10 months ago
parent 3c9373654a
commit b0a37baf94

@ -44,6 +44,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Application.Contracts"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.SqlSugarCore", "src\NPin.SqlSugarCore\NPin.SqlSugarCore.csproj", "{DC8E2E59-589F-4521-95E2-9CE7E1DD5541}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "audit-logging", "audit-logging", "{29EA07EB-E1D2-4BCA-9EA4-D69E28F14978}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "upms", "upms", "{CA0606BE-4146-4390-86CD-AD92FC161A9E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.AuditLogging.Domain.Shared", "module\NPin.Framework.AuditLogging.Domain.Shared\NPin.Framework.AuditLogging.Domain.Shared.csproj", "{67D8E078-69EC-4D2C-8B12-BDB66FB45B58}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.AuditLogging.Domain", "module\NPin.Framework.AuditLogging.Domain\NPin.Framework.AuditLogging.Domain.csproj", "{CF3EECF5-EE94-47F4-978B-2F648B02F99F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.AuditLogging.SqlSugarCore", "module\NPin.Framework.AuditLogging.SqlSugarCore\NPin.Framework.AuditLogging.SqlSugarCore.csproj", "{E50D68D7-8A0C-451D-9344-EE022054E31E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -110,6 +120,18 @@ Global
{DC8E2E59-589F-4521-95E2-9CE7E1DD5541}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC8E2E59-589F-4521-95E2-9CE7E1DD5541}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC8E2E59-589F-4521-95E2-9CE7E1DD5541}.Release|Any CPU.Build.0 = Release|Any CPU
{67D8E078-69EC-4D2C-8B12-BDB66FB45B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67D8E078-69EC-4D2C-8B12-BDB66FB45B58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67D8E078-69EC-4D2C-8B12-BDB66FB45B58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67D8E078-69EC-4D2C-8B12-BDB66FB45B58}.Release|Any CPU.Build.0 = Release|Any CPU
{CF3EECF5-EE94-47F4-978B-2F648B02F99F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF3EECF5-EE94-47F4-978B-2F648B02F99F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF3EECF5-EE94-47F4-978B-2F648B02F99F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF3EECF5-EE94-47F4-978B-2F648B02F99F}.Release|Any CPU.Build.0 = Release|Any CPU
{E50D68D7-8A0C-451D-9344-EE022054E31E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E50D68D7-8A0C-451D-9344-EE022054E31E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E50D68D7-8A0C-451D-9344-EE022054E31E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E50D68D7-8A0C-451D-9344-EE022054E31E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{088B4948-AE5B-45B3-A9FA-B853671CFA05} = {F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C}
@ -127,5 +149,10 @@ Global
{34CEB35B-97EF-41DB-B10B-DC83B2453E9D} = {86F61EBB-4ACC-459C-AB3C-C8D486C3017D}
{C4D324BB-01B6-436F-9AA6-136C7D5438F6} = {86F61EBB-4ACC-459C-AB3C-C8D486C3017D}
{DC8E2E59-589F-4521-95E2-9CE7E1DD5541} = {86F61EBB-4ACC-459C-AB3C-C8D486C3017D}
{29EA07EB-E1D2-4BCA-9EA4-D69E28F14978} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869}
{CA0606BE-4146-4390-86CD-AD92FC161A9E} = {EEAD0AD4-0F90-46D9-A775-D88AE07E2869}
{67D8E078-69EC-4D2C-8B12-BDB66FB45B58} = {29EA07EB-E1D2-4BCA-9EA4-D69E28F14978}
{CF3EECF5-EE94-47F4-978B-2F648B02F99F} = {29EA07EB-E1D2-4BCA-9EA4-D69E28F14978}
{E50D68D7-8A0C-451D-9344-EE022054E31E} = {29EA07EB-E1D2-4BCA-9EA4-D69E28F14978}
EndGlobalSection
EndGlobal

@ -9,7 +9,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="MiniExcel" Version="1.31.3" />
<PackageReference Include="Volo.Abp.Ddd.Application" Version="8.1.0-rc.1" />
<PackageReference Include="Volo.Abp.Ddd.Application" Version="8.0.4" />
</ItemGroup>
</Project>

@ -0,0 +1,19 @@
namespace NPin.Framework.AuditLogging.Domain.Shared.Consts;
public class AuditLogActionConsts
{
/// <summary>
/// Default value: 256
/// </summary>
public static int MaxServiceNameLength { get; set; } = 256;
/// <summary>
/// Default value: 128
/// </summary>
public static int MaxMethodNameLength { get; set; } = 128;
/// <summary>
/// Default value: 2000
/// </summary>
public static int MaxParametersLength { get; set; } = 2000;
}

@ -0,0 +1,59 @@
namespace NPin.Framework.AuditLogging.Domain.Shared.Consts;
public class AuditLogConsts
{
/// <summary>
/// Default value: 96
/// </summary>
public static int MaxApplicationNameLength { get; set; } = 96;
/// <summary>
/// Default value: 64
/// </summary>
public static int MaxClientIpAddressLength { get; set; } = 64;
/// <summary>
/// Default value: 128
/// </summary>
public static int MaxClientNameLength { get; set; } = 128;
/// <summary>
/// Default value: 64
/// </summary>
public static int MaxClientIdLength { get; set; } = 64;
/// <summary>
/// Default value: 64
/// </summary>
public static int MaxCorrelationIdLength { get; set; } = 64;
/// <summary>
/// Default value: 512
/// </summary>
public static int MaxBrowserInfoLength { get; set; } = 512;
/// <summary>
/// Default value: 256
/// </summary>
public static int MaxCommentsLength { get; set; } = 256;
/// <summary>
/// Default value: 256
/// </summary>
public static int MaxUrlLength { get; set; } = 256;
/// <summary>
/// Default value: 16
/// </summary>
public static int MaxHttpMethodLength { get; set; } = 16;
/// <summary>
/// Default value: 256
/// </summary>
public static int MaxUserNameLength { get; set; } = 256;
/// <summary>
/// Default value: 64
/// </summary>
public static int MaxTenantNameLength { get; set; } = 64;
}

@ -0,0 +1,14 @@
namespace NPin.Framework.AuditLogging.Domain.Shared.Consts;
public class EntityChangeConsts
{
/// <summary>
/// Default value: 128
/// </summary>
public static int MaxEntityTypeFullNameLength { get; set; } = 128;
/// <summary>
/// Default value: 128
/// </summary>
public static int MaxEntityIdLength { get; set; } = 128;
}

@ -0,0 +1,24 @@
namespace NPin.Framework.AuditLogging.Domain.Shared.Consts;
public class EntityPropertyChangeConsts
{
/// <summary>
/// Default value: 512
/// </summary>
public static int MaxNewValueLength { get; set; } = 512;
/// <summary>
/// Default value: 512
/// </summary>
public static int MaxOriginalValueLength { get; set; } = 512;
/// <summary>
/// Default value: 128
/// </summary>
public static int MaxPropertyNameLength { get; set; } = 128;
/// <summary>
/// Default value: 64
/// </summary>
public static int MaxPropertyTypeFullNameLength { get; set; } = 64;
}

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="8.0.4" />
</ItemGroup>
</Project>

@ -0,0 +1,10 @@
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
namespace NPin.Framework.AuditLogging.Domain.Shared;
[DependsOn(typeof(AbpDddDomainSharedModule))]
public class NPinFrameworkAuditLoggingDomainSharedModule: AbpModule
{
}

@ -0,0 +1,88 @@
using NPin.Framework.AuditLogging.Domain.Entities;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.Json;
namespace NPin.Framework.AuditLogging.Domain;
public class AuditLogInfoToAuditLogConverter : IAuditLogInfoToAuditLogConverter, ITransientDependency
{
protected IGuidGenerator GuidGenerator { get; }
protected IExceptionToErrorInfoConverter ExceptionToErrorInfoConverter { get; }
protected IJsonSerializer JsonSerializer { get; }
protected AbpExceptionHandlingOptions ExceptionHandlingOptions { get; }
public AuditLogInfoToAuditLogConverter(IGuidGenerator guidGenerator,
IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IJsonSerializer jsonSerializer,
AbpExceptionHandlingOptions exceptionHandlingOptions)
{
GuidGenerator = guidGenerator;
ExceptionToErrorInfoConverter = exceptionToErrorInfoConverter;
JsonSerializer = jsonSerializer;
ExceptionHandlingOptions = exceptionHandlingOptions;
}
public virtual Task<AuditLogAggregateRoot> ConvertAsync(AuditLogInfo auditLogInfo)
{
var auditLogId = GuidGenerator.Create();
var extraProperties = new ExtraPropertyDictionary();
foreach (var pair in auditLogInfo.ExtraProperties)
{
extraProperties.Add(pair.Key, pair.Value);
}
var entityChanges = auditLogInfo.EntityChanges?
.Select(info => new EntityChangeEntity(GuidGenerator, auditLogId, info, auditLogInfo.TenantId))
.ToList() ?? [];
var actions = auditLogInfo.Actions?
.Select(info => new AuditLogActionEntity(GuidGenerator.Create(), auditLogId, info, auditLogInfo.TenantId))
.ToList() ?? [];
var remoteServiceErrorInfos = auditLogInfo.Exceptions?
.Select(ex => ExceptionToErrorInfoConverter.Convert(ex, options =>
{
options.SendExceptionsDetailsToClients = ExceptionHandlingOptions.SendExceptionsDetailsToClients;
options.SendStackTraceToClients = ExceptionHandlingOptions.SendStackTraceToClients;
})) ?? [];
var exceptions = remoteServiceErrorInfos.Any()
? JsonSerializer.Serialize(remoteServiceErrorInfos, indented: true)
: null;
var comments = auditLogInfo.Comments?
.JoinAsString(Environment.NewLine);
var auditLog = new AuditLogAggregateRoot(
auditLogId,
auditLogInfo.ApplicationName,
auditLogInfo.UserId,
auditLogInfo.UserName,
auditLogInfo.TenantName,
auditLogInfo.ImpersonatorUserId,
auditLogInfo.ImpersonatorUserName,
auditLogInfo.ImpersonatorTenantId,
auditLogInfo.ImpersonatorTenantName,
auditLogInfo.ExecutionTime,
auditLogInfo.ExecutionDuration,
auditLogInfo.ClientIpAddress,
auditLogInfo.ClientName,
auditLogInfo.ClientId,
auditLogInfo.CorrelationId,
auditLogInfo.BrowserInfo,
auditLogInfo.HttpMethod,
auditLogInfo.Url,
exceptions,
comments,
auditLogInfo.HttpStatusCode,
auditLogInfo.TenantId,
entityChanges,
actions,
extraProperties
);
return Task.FromResult(auditLog);
}
}

@ -0,0 +1,60 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NPin.Framework.AuditLogging.Domain.Repositories;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;
namespace NPin.Framework.AuditLogging.Domain;
public class AuditingStore : IAuditingStore, ITransientDependency
{
public ILogger<AuditingStore> Logger { get; set; }
protected IAuditLogRepository AuditLogRepository { get; }
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditLogInfoToAuditLogConverter Converter { get; }
public AuditingStore(ILogger<AuditingStore> logger, IAuditLogRepository auditLogRepository,
IUnitOfWorkManager unitOfWorkManager, AbpAuditingOptions options, IAuditLogInfoToAuditLogConverter converter)
{
Logger = logger;
AuditLogRepository = auditLogRepository;
UnitOfWorkManager = unitOfWorkManager;
Options = options;
Converter = converter;
}
public virtual async Task SaveAsync(AuditLogInfo auditInfo)
{
if (!Options.HideErrors)
{
await SaveLogAsync(auditInfo);
return;
}
try
{
await SaveLogAsync(auditInfo);
}
catch (Exception ex)
{
Logger.LogWarning($"无法保存审计日志: {Environment.NewLine}{auditInfo}");
Logger.LogException(ex, LogLevel.Error);
}
}
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
{
var timeConverter = new IsoDateTimeConverter
{
DateTimeFormat = "yyyy-MM-dd HH:mm:ss"
};
Logger.LogDebug($"NPin-请求日志:{JsonConvert.SerializeObject(auditInfo, Formatting.Indented, timeConverter)}");
using var uow = UnitOfWorkManager.Begin(true);
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
await uow.CompleteAsync();
}
}

@ -0,0 +1,51 @@
using NPin.Framework.AuditLogging.Domain.Shared.Consts;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace NPin.Framework.AuditLogging.Domain.Entities;
[DisableAuditing]
[SugarTable("NPinAuditLogAction", "审计日志操作表")]
[SugarIndex($"index_{nameof(AuditLogId)}", nameof(AuditLogId), OrderByType.Asc)]
[SugarIndex($"index_{nameof(TenantId)}_{nameof(ExecutionTime)}", nameof(TenantId), OrderByType.Asc, nameof(ServiceName),
OrderByType.Asc, nameof(MethodName), OrderByType.Asc, nameof(ExecutionTime), OrderByType.Asc)]
public class AuditLogActionEntity : Entity<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid AuditLogId { get; protected set; }
public virtual string? ServiceName { get; protected set; }
public virtual string? MethodName { get; protected set; }
public virtual string? Parameters { get; protected set; }
public virtual DateTime? ExecutionTime { get; protected set; }
public virtual int? ExecutionDuration { get; protected set; }
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public AuditLogActionEntity()
{
}
public AuditLogActionEntity(Guid id, Guid auditLogId, AuditLogActionInfo actionInfo, Guid? tenantId = null)
{
Id = id;
TenantId = tenantId;
AuditLogId = auditLogId;
ExecutionTime = actionInfo.ExecutionTime;
ExecutionDuration = actionInfo.ExecutionDuration;
ServiceName = actionInfo.ServiceName.TruncateFromBeginning(AuditLogActionConsts.MaxServiceNameLength);
MethodName = actionInfo.MethodName.TruncateFromBeginning(AuditLogActionConsts.MaxMethodNameLength);
Parameters = actionInfo.Parameters.Length > AuditLogActionConsts.MaxParametersLength
? ""
: actionInfo.Parameters;
}
}

@ -0,0 +1,111 @@
using NPin.Framework.AuditLogging.Domain.Shared.Consts;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace NPin.Framework.AuditLogging.Domain.Entities;
[DisableAuditing]
[SugarTable("NPinAuditLog", "审计日志")]
[SugarIndex($"index_{nameof(ExecutionTime)}", nameof(TenantId), OrderByType.Asc, nameof(ExecutionTime),
OrderByType.Asc)]
[SugarIndex($"index_{nameof(ExecutionTime)}_{nameof(UserId)}", nameof(TenantId), OrderByType.Asc, nameof(UserId),
OrderByType.Asc, nameof(ExecutionTime), OrderByType.Asc)]
public class AuditLogAggregateRoot : AggregateRoot<Guid>, IMultiTenant
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public virtual string? ApplicationName { get; set; }
public virtual Guid? UserId { get; protected set; }
public virtual string? UserName { get; protected set; }
public virtual string? TenantName { get; protected set; }
public virtual Guid? ImpersonatorUserId { get; protected set; }
public virtual string? ImpersonatorUserName { get; protected set; }
public virtual Guid? ImpersonatorTenantId { get; protected set; }
public virtual string? ImpersonatorTenantName { get; protected set; }
public virtual DateTime? ExecutionTime { get; protected set; }
public virtual int? ExecutionDuration { get; protected set; }
public virtual string? ClientIpAddress { get; protected set; }
public virtual string? ClientName { get; protected set; }
public virtual string? ClientId { get; set; }
public virtual string? CorrelationId { get; set; }
public virtual string? BrowserInfo { get; protected set; }
public virtual string? HttpMethod { get; protected set; }
public virtual string? Url { get; protected set; }
public virtual string? Exceptions { get; protected set; }
public virtual string? Comments { get; protected set; }
public virtual int? HttpStatusCode { get; set; }
public virtual Guid? TenantId { get; protected set; }
// 导航属性
[Navigate(NavigateType.OneToMany, nameof(EntityChangeEntity.AuditLogId))]
public virtual List<EntityChangeEntity> EntityChanges { get; protected set; }
// 导航属性
[Navigate(NavigateType.OneToMany, nameof(AuditLogActionEntity.AuditLogId))]
public virtual List<AuditLogActionEntity> Actions { get; protected set; }
[SugarColumn(IsIgnore = true)] public override ExtraPropertyDictionary ExtraProperties { get; protected set; }
public AuditLogAggregateRoot()
{
}
public AuditLogAggregateRoot(Guid id, string? applicationName, Guid? userId, string? userName, string? tenantName,
Guid? impersonatorUserId, string? impersonatorUserName, Guid? impersonatorTenantId,
string? impersonatorTenantName, DateTime? executionTime, int? executionDuration, string? clientIpAddress,
string? clientName, string? clientId, string? correlationId, string? browserInfo, string? httpMethod,
string? url, string? exceptions, string? comments, int? httpStatusCode, Guid? tenantId,
List<EntityChangeEntity> entityChanges, List<AuditLogActionEntity> actions,
ExtraPropertyDictionary extraProperties) : base(id)
{
ApplicationName = applicationName.Truncate(AuditLogConsts.MaxApplicationNameLength);
UserId = userId;
UserName = userName.Truncate(AuditLogConsts.MaxUserNameLength);
TenantName = tenantName.Truncate(AuditLogConsts.MaxTenantNameLength);
ImpersonatorUserId = impersonatorUserId;
ImpersonatorUserName = impersonatorUserName.Truncate(AuditLogConsts.MaxUserNameLength);
ImpersonatorTenantId = impersonatorTenantId;
ImpersonatorTenantName = impersonatorTenantName.Truncate(AuditLogConsts.MaxTenantNameLength);
ExecutionTime = executionTime;
ExecutionDuration = executionDuration;
ClientIpAddress = clientIpAddress.Truncate(AuditLogConsts.MaxClientIpAddressLength);
ClientName = clientName.Truncate(AuditLogConsts.MaxClientNameLength);
ClientId = clientId.Truncate(AuditLogConsts.MaxClientIdLength);
CorrelationId = correlationId.Truncate(AuditLogConsts.MaxCorrelationIdLength);
BrowserInfo = browserInfo.Truncate(AuditLogConsts.MaxBrowserInfoLength);
HttpMethod = httpMethod.Truncate(AuditLogConsts.MaxHttpMethodLength);
Url = url.Truncate(AuditLogConsts.MaxUrlLength);
Exceptions = exceptions;
Comments = comments.Truncate(AuditLogConsts.MaxCommentsLength);
HttpStatusCode = httpStatusCode;
TenantId = tenantId;
EntityChanges = entityChanges;
Actions = actions;
ExtraProperties = extraProperties;
}
}

@ -0,0 +1,63 @@
using NPin.Framework.AuditLogging.Domain.Shared.Consts;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
namespace NPin.Framework.AuditLogging.Domain.Entities;
[SugarTable("NPinEntityChange")]
[SugarIndex($"index_{nameof(AuditLogId)}", nameof(AuditLogId), OrderByType.Asc)]
[SugarIndex($"index_{nameof(TenantId)}_{nameof(EntityId)}", nameof(TenantId), OrderByType.Asc,
nameof(EntityTypeFullName), OrderByType.Asc, nameof(EntityId), OrderByType.Asc)]
public class EntityChangeEntity : Entity<Guid>, IMultiTenant
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public virtual Guid AuditLogId { get; protected set; }
public virtual Guid? TenantId { get; protected set; }
public virtual DateTime? ChangeTime { get; protected set; }
public virtual EntityChangeType? ChangeType { get; protected set; }
public virtual Guid? EntityTenantId { get; protected set; }
public virtual string? EntityId { get; protected set; }
public virtual string? EntityTypeFullName { get; protected set; }
// 关联
[Navigate(NavigateType.OneToMany, nameof(EntityPropertyChangeEntity.EntityChangeId))]
public virtual List<EntityPropertyChangeEntity> PropertyChanges { get; protected set; }
public EntityChangeEntity()
{
}
public EntityChangeEntity(
IGuidGenerator guidGenerator,
Guid auditLogId,
EntityChangeInfo entityChangeInfo,
Guid? tenantId = null)
{
Id = guidGenerator.Create();
AuditLogId = auditLogId;
TenantId = tenantId;
ChangeTime = entityChangeInfo.ChangeTime;
ChangeType = entityChangeInfo.ChangeType;
EntityTenantId = entityChangeInfo.EntityTenantId;
EntityId = entityChangeInfo.EntityId.Truncate(EntityChangeConsts.MaxEntityTypeFullNameLength);
EntityTypeFullName =
entityChangeInfo.EntityTypeFullName.TruncateFromBeginning(EntityChangeConsts.MaxEntityTypeFullNameLength);
PropertyChanges = entityChangeInfo
.PropertyChanges?
.Select(p => new EntityPropertyChangeEntity(guidGenerator, Id, p, tenantId))
.ToList()
?? new List<EntityPropertyChangeEntity>();
}
}

@ -0,0 +1,50 @@
using NPin.Framework.AuditLogging.Domain.Shared.Consts;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
namespace NPin.Framework.AuditLogging.Domain.Entities;
[SugarTable("NPinEntityPropertyChange")]
[SugarIndex($"index_{nameof(EntityChangeId)}", nameof(EntityChangeId), OrderByType.Asc)]
public class EntityPropertyChangeEntity : Entity<Guid>, IMultiTenant
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public virtual Guid? TenantId { get; protected set; }
public virtual Guid? EntityChangeId { get; protected set; }
public virtual string? NewValue { get; protected set; }
public virtual string? OriginalValue { get; protected set; }
public virtual string? PropertyName { get; protected set; }
public virtual string? PropertyTypeFullName { get; protected set; }
public EntityPropertyChangeEntity()
{
}
public EntityPropertyChangeEntity(
IGuidGenerator guidGenerator,
Guid entityChangeId,
EntityPropertyChangeInfo entityChangeInfo,
Guid? tenantId = null)
{
Id = guidGenerator.Create();
TenantId = tenantId;
EntityChangeId = entityChangeId;
NewValue = entityChangeInfo.NewValue.Truncate(EntityPropertyChangeConsts.MaxNewValueLength);
OriginalValue = entityChangeInfo.OriginalValue.Truncate(EntityPropertyChangeConsts.MaxOriginalValueLength);
PropertyName =
entityChangeInfo.PropertyName.TruncateFromBeginning(EntityPropertyChangeConsts.MaxPropertyNameLength);
PropertyTypeFullName =
entityChangeInfo.PropertyTypeFullName.TruncateFromBeginning(EntityPropertyChangeConsts
.MaxPropertyTypeFullNameLength);
}
}

@ -0,0 +1,10 @@
using NPin.Framework.AuditLogging.Domain.Entities;
namespace NPin.Framework.AuditLogging.Domain.Events;
public class EntityChangeWithUsername
{
public EntityChangeEntity EntityChange { get; set; }
public string Username { get; set; }
}

@ -0,0 +1,12 @@
using NPin.Framework.AuditLogging.Domain.Entities;
using Volo.Abp.Auditing;
namespace NPin.Framework.AuditLogging.Domain;
/// <summary>
/// 审计日志转换器接口
/// </summary>
public interface IAuditLogInfoToAuditLogConverter
{
Task<AuditLogAggregateRoot> ConvertAsync(AuditLogInfo auditLogInfo);
}

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Auditing" Version="8.0.4" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\framework\NPin.Framework.SqlSugarCore.Abstractions\NPin.Framework.SqlSugarCore.Abstractions.csproj" />
<ProjectReference Include="..\NPin.Framework.AuditLogging.Domain.Shared\NPin.Framework.AuditLogging.Domain.Shared.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,15 @@
using NPin.Framework.AuditLogging.Domain.Shared;
using Volo.Abp.Auditing;
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
namespace NPin.Framework.AuditLogging.Domain;
[DependsOn(
typeof(NPinFrameworkAuditLoggingDomainSharedModule),
typeof(AbpDddDomainModule),
typeof(AbpAuditingModule)
)]
public class NPinFrameworkAuditLoggingDomainModule : AbpModule
{
}

@ -0,0 +1,131 @@
using System.Net;
using NPin.Framework.AuditLogging.Domain.Entities;
using NPin.Framework.AuditLogging.Domain.Events;
using NPin.Framework.SqlSugarCore.Abstractions;
using Volo.Abp.Auditing;
namespace NPin.Framework.AuditLogging.Domain.Repositories;
public interface IAuditLogRepository : ISqlSugarRepository<AuditLogAggregateRoot, Guid>
{
/// <summary>
/// 获取每日平均执行时长
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate, DateTime endDate,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取数量
/// </summary>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <param name="httpMethod"></param>
/// <param name="url"></param>
/// <param name="userId"></param>
/// <param name="userName"></param>
/// <param name="applicationName"></param>
/// <param name="clientIpAddress"></param>
/// <param name="correlationId"></param>
/// <param name="maxExecutionDuration"></param>
/// <param name="minExecutionDuration"></param>
/// <param name="hasException"></param>
/// <param name="httpStatusCode"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<long> GetCountAsync(DateTime? startTime = null, DateTime? endTime = null, string httpMethod = null,
string url = null, Guid? userId = null, string userName = null, string applicationName = null,
string clientIpAddress = null, string correlationId = null, int? maxExecutionDuration = null,
int? minExecutionDuration = null, bool? hasException = null, HttpStatusCode? httpStatusCode = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取实体改变日志
/// </summary>
/// <param name="entityChangeId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<EntityChangeEntity> GetEntityChange(Guid entityChangeId, CancellationToken cancellationToken = default);
/// <summary>
/// 获取实体改变日志数量
/// </summary>
/// <param name="auditLogId"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <param name="changeType"></param>
/// <param name="entityId"></param>
/// <param name="entityTypeFullName"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<long> GetEntityChangeCountAsync(Guid? auditLogId = null, DateTime? startTime = null, DateTime? endTime = null,
EntityChangeType? changeType = null, string entityId = null, string entityTypeFullName = null,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取实体改变日志列表
/// </summary>
/// <param name="sorting"></param>
/// <param name="maxResultCount"></param>
/// <param name="skipCount"></param>
/// <param name="auditLogId"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <param name="changeType"></param>
/// <param name="entityId"></param>
/// <param name="entityTypeFullName"></param>
/// <param name="includeDetails"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<List<EntityChangeEntity>> GetEntityChangeListAsync(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);
/// <summary>
/// 获取用户名改变日志列表
/// </summary>
/// <param name="entityId"></param>
/// <param name="entityTypeFullName"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<List<EntityChangeWithUsername>> GetEntityChangesWithUsernameAsync(string entityId, string entityTypeFullName,
CancellationToken cancellationToken = default);
/// <summary>
/// 获取用户名改变日志
/// </summary>
/// <param name="entityChangeId"></param>
/// <returns></returns>
Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(Guid entityChangeId);
/// <summary>
/// 获取审计日志列表
/// </summary>
/// <param name="sorting"></param>
/// <param name="maxResultCount"></param>
/// <param name="skipCount"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <param name="httpMethod"></param>
/// <param name="url"></param>
/// <param name="userId"></param>
/// <param name="userName"></param>
/// <param name="applicationName"></param>
/// <param name="clientIpAddress"></param>
/// <param name="correlationId"></param>
/// <param name="maxExecutionDuration"></param>
/// <param name="minExecutionDuration"></param>
/// <param name="hasException"></param>
/// <param name="httpStatusCode"></param>
/// <param name="includeDetails"></param>
/// <returns></returns>
Task<List<AuditLogAggregateRoot>> GetListAsync(string sorting = null, int maxResultCount = 50, int skipCount = 0,
DateTime? startTime = null, DateTime? endTime = null, string httpMethod = null, string url = null,
Guid? userId = null, string userName = null, string applicationName = null, string clientIpAddress = null,
string correlationId = null, int? maxExecutionDuration = null, int? minExecutionDuration = null,
bool? hasException = null, HttpStatusCode? httpStatusCode = null, bool includeDetails = false);
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\framework\NPin.Framework.Mapster\NPin.Framework.Mapster.csproj" />
<ProjectReference Include="..\..\framework\NPin.Framework.SqlSugarCore\NPin.Framework.SqlSugarCore.csproj" />
<ProjectReference Include="..\NPin.Framework.AuditLogging.Domain\NPin.Framework.AuditLogging.Domain.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using NPin.Framework.AuditLogging.Domain;
using NPin.Framework.AuditLogging.Domain.Repositories;
using NPin.Framework.AuditLogging.SqlSugarCore.Repositories;
using NPin.Framework.SqlSugarCore;
using Volo.Abp.Modularity;
namespace NPin.Framework.AuditLogging.SqlSugarCore;
[DependsOn(
typeof(NPinFrameworkAuditLoggingDomainModule),
typeof(NPinFrameworkSqlSugarCoreModule)
)]
public class NPinFrameworkAuditLoggingSqlSugarCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient<IAuditLogRepository, AuditLogRepository>();
}
}

@ -0,0 +1,238 @@
using System.Net;
using NPin.Framework.AuditLogging.Domain.Entities;
using NPin.Framework.AuditLogging.Domain.Events;
using NPin.Framework.AuditLogging.Domain.Repositories;
using NPin.Framework.SqlSugarCore.Abstractions;
using NPin.Framework.SqlSugarCore.Repositories;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
namespace NPin.Framework.AuditLogging.SqlSugarCore.Repositories;
public class AuditLogRepository : SqlSugarRepository<AuditLogAggregateRoot, Guid>, IAuditLogRepository
{
public AuditLogRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(
sugarDbContextProvider)
{
}
/// <summary>
/// 重写插入逻辑以支持关联关系(导航)
/// </summary>
/// <param name="insertObj"></param>
/// <returns></returns>
public override async Task<bool> InsertAsync(AuditLogAggregateRoot insertObj)
{
return await Db.InsertNav(insertObj)
.Include(z1 => z1.Actions)
// .Include(z1=>z1.EntityChanges)
// .ThenInclude(z2=>z2.PropertyChanges)
.ExecuteCommandAsync();
}
public async Task<Dictionary<DateTime, double>> GetAverageExecutionDurationPerDayAsync(DateTime startDate,
DateTime endDate,
CancellationToken cancellationToken = default)
{
// 分组排序查询
var result = await DbQueryable
.Where(a => a.ExecutionTime < endDate.AddDays(1) && a.ExecutionTime > startDate)
.OrderBy(t => t.ExecutionTime)
.GroupBy(g => new { g.ExecutionTime!.Value.Date })
.Select(s => new
{
Day = SqlFunc.AggregateMin(s.ExecutionTime),
avgExecutionTime = SqlFunc.AggregateAvg(s.ExecutionDuration)
})
.ToListAsync(cancellationToken);
// 散列为Dict
return result.ToDictionary(e => e.Day!.Value.ClearTime(), e => (double)e.avgExecutionTime!);
}
public async Task<List<AuditLogAggregateRoot>> GetListAsync(string sorting = null, int maxResultCount = 50,
int skipCount = 0, DateTime? startTime = null,
DateTime? endTime = null, string httpMethod = null, string url = null, Guid? userId = null,
string userName = null,
string applicationName = null, string clientIpAddress = null, string correlationId = null,
int? maxExecutionDuration = null, int? minExecutionDuration = null, bool? hasException = null,
HttpStatusCode? httpStatusCode = null, bool includeDetails = false)
{
var query = await GetListQueryAsync(
startTime,
endTime,
httpMethod,
url,
userId,
userName,
applicationName,
clientIpAddress,
correlationId,
maxExecutionDuration,
minExecutionDuration,
hasException,
httpStatusCode,
includeDetails
);
var auditLogs = await query
.OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(AuditLogAggregateRoot.ExecutionTime)} DESC" : sorting)
.ToPageListAsync(skipCount, maxResultCount);
return auditLogs;
}
public async Task<long> GetCountAsync(DateTime? startTime = null, DateTime? endTime = null,
string httpMethod = null,
string url = null,
Guid? userId = null, string userName = null, string applicationName = null, string clientIpAddress = null,
string correlationId = null, int? maxExecutionDuration = null, int? minExecutionDuration = null,
bool? hasException = null, HttpStatusCode? httpStatusCode = null, CancellationToken cancellationToken = default)
{
var query = await GetListQueryAsync(
startTime,
endTime,
httpMethod,
url,
userId,
userName,
applicationName,
clientIpAddress,
correlationId,
maxExecutionDuration,
minExecutionDuration,
hasException,
httpStatusCode
);
var totalCount = await query.CountAsync(cancellationToken);
return totalCount;
}
protected virtual async Task<ISugarQueryable<AuditLogAggregateRoot>> GetListQueryAsync(
DateTime? startTime = null,
DateTime? endTime = null,
string httpMethod = null,
string url = null,
Guid? userId = null,
string userName = null,
string applicationName = null,
string clientIpAddress = null,
string correlationId = null,
int? maxExecutionDuration = null,
int? minExecutionDuration = null,
bool? hasException = null,
HttpStatusCode? httpStatusCode = null,
bool includeDetails = false)
{
var nHttpStatusCode = (int?)httpStatusCode;
return DbQueryable
.WhereIF(startTime.HasValue, auditLog => auditLog.ExecutionTime >= startTime)
.WhereIF(endTime.HasValue, auditLog => auditLog.ExecutionTime <= endTime)
.WhereIF(hasException.HasValue && hasException.Value,
auditLog => auditLog.Exceptions != null && auditLog.Exceptions != "")
.WhereIF(hasException.HasValue && !hasException.Value,
auditLog => auditLog.Exceptions == null || auditLog.Exceptions == "")
.WhereIF(httpMethod != null, auditLog => auditLog.HttpMethod == httpMethod)
.WhereIF(url != null, auditLog => auditLog.Url != null && auditLog.Url.Contains(url))
.WhereIF(userId != null, auditLog => auditLog.UserId == userId)
.WhereIF(userName != null, auditLog => auditLog.UserName == userName)
.WhereIF(applicationName != null, auditLog => auditLog.ApplicationName == applicationName)
.WhereIF(clientIpAddress != null,
auditLog => auditLog.ClientIpAddress != null && auditLog.ClientIpAddress == clientIpAddress)
.WhereIF(correlationId != null, auditLog => auditLog.CorrelationId == correlationId)
.WhereIF(httpStatusCode != null && httpStatusCode > 0,
auditLog => auditLog.HttpStatusCode == nHttpStatusCode)
.WhereIF(maxExecutionDuration != null && maxExecutionDuration.Value > 0,
auditLog => auditLog.ExecutionDuration <= maxExecutionDuration)
.WhereIF(minExecutionDuration != null && minExecutionDuration.Value > 0,
auditLog => auditLog.ExecutionDuration >= minExecutionDuration);
}
public async Task<EntityChangeEntity> GetEntityChange(Guid entityChangeId,
CancellationToken cancellationToken = default)
{
var entityChange = await (await GetDbContextAsync()).Queryable<EntityChangeEntity>()
.Where(x => x.Id == entityChangeId)
.OrderBy(x => x.Id)
.FirstAsync(cancellationToken);
if (entityChange == null)
{
throw new EntityNotFoundException(typeof(EntityChangeEntity));
}
return entityChange;
}
public async Task<long> GetEntityChangeCountAsync(Guid? auditLogId = null, DateTime? startTime = null,
DateTime? endTime = null,
EntityChangeType? changeType = null, string entityId = null, string entityTypeFullName = null,
CancellationToken cancellationToken = default)
{
var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId,
entityTypeFullName);
var totalCount = await query.CountAsync(cancellationToken);
return totalCount;
}
public async Task<List<EntityChangeEntity>> GetEntityChangeListAsync(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)
{
var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId,
entityTypeFullName, includeDetails);
return await query
.OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(EntityChangeEntity.ChangeTime)} DESC" : sorting)
.ToPageListAsync(skipCount, maxResultCount, cancellationToken);
}
protected virtual async Task<ISugarQueryable<EntityChangeEntity>> GetEntityChangeListQueryAsync(
Guid? auditLogId = null,
DateTime? startTime = null,
DateTime? endTime = null,
EntityChangeType? changeType = null,
string entityId = null,
string entityTypeFullName = null,
bool includeDetails = false)
{
return (await GetDbContextAsync())
.Queryable<EntityChangeEntity>()
.WhereIF(auditLogId.HasValue, e => e.AuditLogId == auditLogId)
.WhereIF(startTime.HasValue, e => e.ChangeTime >= startTime)
.WhereIF(endTime.HasValue, e => e.ChangeTime <= endTime)
.WhereIF(changeType.HasValue, e => e.ChangeType == changeType)
.WhereIF(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId)
.WhereIF(!string.IsNullOrWhiteSpace(entityTypeFullName),
e => e.EntityTypeFullName.Contains(entityTypeFullName));
}
public async Task<List<EntityChangeWithUsername>> GetEntityChangesWithUsernameAsync(string entityId,
string entityTypeFullName,
CancellationToken cancellationToken = default)
{
var query = (await GetDbContextAsync()).Queryable<EntityChangeEntity>()
.Where(x => x.EntityId == entityId && x.EntityTypeFullName == entityTypeFullName);
var result = await query.LeftJoin<AuditLogAggregateRoot>((x, audit) => x.AuditLogId == audit.Id)
.Select((x, audit) => new EntityChangeWithUsername { EntityChange = x, Username = audit.UserName! })
.OrderByDescending(x => x.EntityChange.ChangeTime)
.ToListAsync(cancellationToken);
return result;
}
public async Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(Guid entityChangeId)
{
var auditLog = await DbQueryable
.Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId))
.FirstAsync();
return new EntityChangeWithUsername
{
EntityChange = auditLog.EntityChanges.First(x => x.Id == entityChangeId),
Username = auditLog.UserName!
};
}
}

@ -1,9 +1,16 @@
using NPin.Application.Contracts;
using NPin.Domain;
using NPin.Framework.Ddd.Application;
using NPin.Framework.Ddd.Application.Contracts;
namespace NPin.Application;
[DependsOn(
typeof(NPinApplicationContractsModule),
typeof(NPinDomainModule),
// TODO rbac bbs tenant codegen
typeof(NPinFrameworkDddApplicationModule)
)]
public class NPinApplicationModule: AbpModule
{
}

@ -9,6 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="8.0.4" />
<PackageReference Include="Volo.Abp.Users.Domain.Shared" Version="8.0.4" />
</ItemGroup>
</Project>

@ -1,11 +1,11 @@
namespace NPin.Domain.Shared;
[DependsOn(
typeof(NPinDomainSharedModule))
// TODO RBAC
// TODO RBAC
// TODO BBS
// TODO AuditLogging
]
// TODO AbpDddDomainSharedModule
)]
public class NPinDomainSharedModule : AbpModule
{
}

@ -0,0 +1,20 @@
using NPin.Domain.Shared;
using NPin.Framework.Mapster;
using Volo.Abp.Caching;
using Volo.Abp.Domain;
namespace NPin.Domain;
[DependsOn(
typeof(NPinDomainSharedModule),
// TODO Tenant
// TODO Rbac
// TODO Bbs
// TODO Audit
typeof(NPinFrameworkMapsterModule),
typeof(AbpDddDomainModule),
typeof(AbpCachingModule)
)]
public class NPinDomainModule : AbpModule
{
}

@ -9,6 +9,7 @@
<ItemGroup>
<ProjectReference Include="..\..\framework\NPin.Framework.Mapster\NPin.Framework.Mapster.csproj" />
<ProjectReference Include="..\..\framework\NPin.Framework.SqlSugarCore\NPin.Framework.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\NPin.Framework.AuditLogging.SqlSugarCore\NPin.Framework.AuditLogging.SqlSugarCore.csproj" />
<ProjectReference Include="..\NPin.Domain\NPin.Domain.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,6 @@
namespace NPin.SqlSugarCore;
public class NPinDbContext
{
}

@ -0,0 +1,24 @@
using NPin.Domain;
using NPin.Framework.AuditLogging.SqlSugarCore;
using NPin.Framework.Mapster;
using NPin.Framework.SqlSugarCore;
namespace NPin.SqlSugarCore;
[DependsOn(
typeof(NPinDomainModule),
// TODO rbac bbs codegen
typeof(NPinFrameworkAuditLoggingSqlSugarCoreModule),
// TODO tenant
typeof(NPinFrameworkMapsterModule),
typeof(NPinFrameworkSqlSugarCoreModule)
)]
public class NPinSqlSugarCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// context.Services.AddNPinDbContext<NPinDbContext>();
// 默认不开放可根据项目需要是否直接对外开放db
// context.Services.AddTransient(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient);
}
}

@ -21,6 +21,8 @@
<ItemGroup>
<ProjectReference Include="..\..\Framework\NPin.Framework.AspNetCore.Authentication.OAuth\NPin.Framework.AspNetCore.Authentication.OAuth.csproj"/>
<ProjectReference Include="..\..\Framework\NPin.Framework.AspNetCore\NPin.Framework.AspNetCore.csproj"/>
<ProjectReference Include="..\NPin.Application\NPin.Application.csproj" />
<ProjectReference Include="..\NPin.SqlSugarCore\NPin.SqlSugarCore.csproj" />
</ItemGroup>
<ItemGroup>

@ -1,10 +1,181 @@
using Volo.Abp.Modularity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Cors;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Converters;
using NPin.Application;
using NPin.Framework.AspNetCore;
using NPin.Framework.AspNetCore.Authentication.OAuth;
using NPin.Framework.AspNetCore.Authentication.OAuth.Gitee;
using NPin.Framework.AspNetCore.Authentication.OAuth.QQ;
using NPin.Framework.AspNetCore.Microsoft.AspNetCore.Builder;
using NPin.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection;
using NPin.SqlSugarCore;
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
using Volo.Abp.Auditing;
using Volo.Abp.Autofac;
using Volo.Abp.Caching;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Swashbuckle;
namespace NPin;
// [DependsOn(
// typeof(NPin))]
public class NPinWebModule: AbpModule
[DependsOn(
typeof(NPinSqlSugarCoreModule),
typeof(NPinApplicationModule),
// Abp modules
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAutofacModule),
typeof(AbpSwashbuckleModule),
typeof(AbpAuditingModule),
typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
// Framework modules
typeof(NPinFrameworkAspNetCoreModule),
typeof(NPinFrameworkAspNetCoreAuthenticationOAuthModule)
)]
public class NPinWebModule : AbpModule
{
private const string DefaultCorsPolicyName = "Default";
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var services = context.Services;
// 请求日志
Configure<AbpAuditingOptions>(opt =>
{
// 默认关闭,开启后有大量的审计日志
opt.IsEnabled = false;
// 审计日志过滤器
opt.AlwaysLogSelectors.Add(_ => Task.FromResult(true));
});
// 动态API
Configure<AbpAspNetCoreMvcOptions>(opt =>
{
opt.ConventionalControllers.Create(typeof(NPinApplicationModule).Assembly,
opts => opts.RemoteServiceName = "default");
// TODO 添加其它模块的动态API
// Rbac bbs tenant code-gen
});
// Api格式配置
services.AddControllers().AddNewtonsoftJson(opt =>
{
// 时间格式
opt.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
// 枚举 <-> string 转换
opt.SerializerSettings.Converters.Add(new StringEnumConverter());
});
// 设置分布式缓存不要过期滑动时间20分钟
Configure<AbpDistributedCacheOptions>(opt =>
{
opt.GlobalCacheEntryOptions.SlidingExpiration = null;
// 缓存key前缀
opt.KeyPrefix = "NPin:";
});
// 关闭CSRF防伪验证
Configure<AbpAntiForgeryOptions>(opt => { opt.AutoValidate = false; });
// 设置 swagger
context.Services.AddNPinSwaggerGen<NPinWebModule>(opt =>
{
opt.SwaggerDoc("default", new OpenApiInfo
{
Title = "NPin.Framework",
Version = "v1",
Description = "NPin.Framework API文档"
});
});
// 配置跨域
context.Services.AddCors(opt =>
{
opt.AddPolicy(DefaultCorsPolicyName, builder =>
{
builder.WithOrigins(
configuration["App:CorsOrigins"]!.Split(";", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
)
.WithAbpExposedHeaders()
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// 配置 多租户
Configure<AbpTenantResolveOptions>(opt =>
{
// 只需要 Header 方式的多租户
// Cookie 方式在 jwt上有坑
opt.TenantResolvers.Clear();
opt.TenantResolvers.Add(new HeaderTenantResolveContributor());
});
// 配置 JWT 鉴权
// var jwtOptions = configuration.GetSection(nameof(JwtOptions))
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt => { })
.AddJwtBearer(opt => { })
.AddQQ(opt => { configuration.GetSection("OAuth:QQ").Bind(opt); })
.AddGitee(opt => { configuration.GetSection("OAuth:Gitee").Bind(opt); });
// 授权
context.Services.AddAuthorization();
return Task.CompletedTask;
}
public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseRouting();
// Cors
app.UseCors(DefaultCorsPolicyName);
// TODO 无感Token refresh
// 多租户
app.UseMultiTenancy();
// Swagger
app.UseNPinSwagger();
// 中间件
app.UseNPinApiMiddleware();
// 静态资源
app.UseStaticFiles("/api/app/wwwroot");
app.UseDefaultFiles();
app.UseDirectoryBrowser("/api/app/wwwroot");
// 工作单元
app.UseUnitOfWork();
// 授权
app.UseAuthorization();
// 审计
app.UseAuditing();
// 日志
app.UseAbpSerilogEnrichers();
// 终端节点
app.UseConfiguredEndpoints();
return Task.CompletedTask;
}
}
Loading…
Cancel
Save