Skip to content

Commit

Permalink
feat: ✨ 登录日志独立存储 (#161)
Browse files Browse the repository at this point in the history
请求日志自动分表
[skip ci]

Co-authored-by: tk <[email protected]>
  • Loading branch information
nsnail and tk authored Jul 26, 2024
1 parent e1bea2e commit faaf5aa
Show file tree
Hide file tree
Showing 74 changed files with 1,500 additions and 469 deletions.
3 changes: 3 additions & 0 deletions assets/res/Fields.ln
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
电子邮箱
登录
登录名
登录日志导出
硕士
示例导出
离异
Expand All @@ -123,6 +125,7 @@
请求日志导出
调试
跟踪
跟踪标识
跟踪编号
身份证
运行
Expand Down
2 changes: 1 addition & 1 deletion build/code.quality.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.29.0.95321">
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.30.0.95878">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
19 changes: 14 additions & 5 deletions src/backend/NetAdmin.AdmServer.Tests/AllTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -823,11 +823,14 @@ public Task GenerateJsCodeAsync()
/// <inheritdoc />
[InlineData(default)]
[Theory]
public async Task<PagedQueryRsp<GetAllEntriesRsp>> GetAllEntriesAsync(PagedQueryReq<GetAllEntriesReq> req)
{
var rsp = await PostAsync("/api/sys/cache/get.all.entries"
, JsonContent.Create(new PagedQueryReq<GetAllEntriesReq>()))
.ConfigureAwait(true);
public Task<IEnumerable<GetEntryRsp>> GetAllEntriesAsync(GetAllEntriesReq req)
{
var rsp = PostAsync("/api/sys/cache/get.all.entries", JsonContent.Create(new GetAllEntriesReq()))
.ConfigureAwait(true)
.GetAwaiter()
#pragma warning disable xUnit1031
.GetResult();
#pragma warning restore xUnit1031
Assert.Equal(HttpStatusCode.OK, rsp.StatusCode);
return default;
}
Expand Down Expand Up @@ -1029,6 +1032,12 @@ public Task<string> GetDicValueAsync(GetDicValueReq req)
return default;
}

/// <inheritdoc />
public Task<GetEntryRsp> GetEntryAsync(GetEntriesReq req)
{
return default;
}

/// <inheritdoc />
[Fact]
public IDictionary<string, Dictionary<string, string[]>> GetEnums()
Expand Down
32 changes: 26 additions & 6 deletions src/backend/NetAdmin.Application/Services/RedLockerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,39 @@ BasicRepository<TEntity, TPrimary> rpo
where TEntity : EntityBase<TPrimary> //
where TPrimary : IEquatable<TPrimary>
{
/// <summary>
/// 获取锁
/// </summary>
protected Task<IRedLock> GetLockerAsync(string lockName)
{
return GetLockerAsync(lockName, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_WAIT)
, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_RETRY_INTERVAL));
}

/// <summary>
/// 获取锁
/// </summary>
/// <exception cref="NetAdminGetLockerException">NetAdminGetLockerException</exception>
protected async Task<IRedLock> GetLockerAsync(string lockName)
protected async Task<IRedLock> GetLockerAsync(string lockName, TimeSpan waitTime, TimeSpan retryInterval)
{
// 加锁
var redLock = await redLocker.RedLockFactory.CreateLockAsync( //
lockName, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_EXPIRY)
, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_WAIT)
, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_RETRY))
.ConfigureAwait(false);
var lockTask = waitTime == default || retryInterval == default
? redLocker.RedLockFactory.CreateLockAsync(lockName, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_EXPIRY))
: redLocker.RedLockFactory.CreateLockAsync( //
lockName, TimeSpan.FromSeconds(Numbers.SECS_RED_LOCK_EXPIRY), waitTime, retryInterval);
var redLock = await lockTask.ConfigureAwait(false);

return redLock.IsAcquired ? redLock : throw new NetAdminGetLockerException();
}

/// <summary>
/// 获取锁
/// </summary>
/// <remarks>
/// 不重试,失败直接抛出异常
/// </remarks>
protected Task<IRedLock> GetLockerOnceAsync(string lockName)
{
return GetLockerAsync(lockName, default, default);
}
}
10 changes: 10 additions & 0 deletions src/backend/NetAdmin.Domain/Contexts/ContextUserToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,14 @@ public static ContextUserToken Create(QueryUserRsp user)
Id = user.Id, Token = user.Token, UserName = user.UserName, DeptId = user.DeptId
};
}

/// <summary>
/// 从 Json Web Token 创建上下文用户
/// </summary>
public static ContextUserToken Create(string jwt)
{
var claim = JWTEncryption.ReadJwtToken(jwt.TrimPrefix($"{Chars.FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA} "))
?.Claims.FirstOrDefault(x => x.Type == nameof(ContextUserToken));
return claim?.Value.ToObject<ContextUserToken>();
}
}
2 changes: 1 addition & 1 deletion src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public record Sys_Api : ImmutableEntity<string>, IFieldSummary
[Column]
[CsvIgnore]
[JsonIgnore]
public int PathCrc32 { get; init; }
public virtual int PathCrc32 { get; init; }

/// <summary>
/// 角色集合
Expand Down
150 changes: 150 additions & 0 deletions src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_LoginLog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
namespace NetAdmin.Domain.DbMaps.Sys;

/// <summary>
/// 登录日志表
/// </summary>
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(OwnerId), nameof(OwnerId), false)]
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_LoginLog))]
public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFieldCreatedClientIp
, IFieldCreatedClientUserAgent
{
/// <inheritdoc />
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual int? CreatedClientIp { get; init; }

/// <inheritdoc />
[Column(ServerTime = DateTimeKind.Local, CanUpdate = false, Position = -1)]
[CsvIgnore]
[JsonIgnore]
public virtual DateTime CreatedTime { get; init; }

/// <inheritdoc />
#if DBTYPE_SQLSERVER
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_1022)]
#else
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[CsvIgnore]
[JsonIgnore]
public virtual string CreatedUserAgent { get; init; }

/// <summary>
/// 执行耗时(毫秒)
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual int Duration { get; init; }

/// <summary>
/// 程序响应码
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual ErrorCodes ErrorCode { get; init; }

/// <summary>
/// HTTP状态码
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_SMALL_INT)]
[CsvIgnore]
[JsonIgnore]
public virtual int HttpStatusCode { get; init; }

/// <summary>
/// 登录用户名
/// </summary>
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)]
[CsvIgnore]
[JsonIgnore]
public virtual string LoginUserName { get; init; }

/// <summary>
/// 拥有者
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public Sys_User Owner { get; init; }

/// <inheritdoc />
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerDeptId { get; init; }

/// <inheritdoc />
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }

/// <summary>
/// 请求内容
/// </summary>
#if DBTYPE_SQLSERVER
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[CsvIgnore]
[JsonIgnore]
public virtual string RequestBody { get; init; }

/// <summary>
/// 请求头信息
/// </summary>
#if DBTYPE_SQLSERVER
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[CsvIgnore]
[JsonIgnore]
public virtual string RequestHeaders { get; init; }

/// <summary>
/// 请求地址
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)]
[CsvIgnore]
[JsonIgnore]
public virtual string RequestUrl { get; init; }

/// <summary>
/// 响应内容
/// </summary>
#if DBTYPE_SQLSERVER
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[CsvIgnore]
[JsonIgnore]
public virtual string ResponseBody { get; init; }

/// <summary>
/// 响应头
/// </summary>
#if DBTYPE_SQLSERVER
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)]
#else
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
#endif
[CsvIgnore]
[JsonIgnore]
public virtual string ResponseHeaders { get; init; }

/// <summary>
/// 服务器IP
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual int? ServerIp { get; init; }
}
20 changes: 15 additions & 5 deletions src/backend/NetAdmin.Domain/DbMaps/Sys/Sys_RequestLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 请求日志表
/// </summary>
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(ApiPathCrc32), nameof(ApiPathCrc32), false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(OwnerId), nameof(OwnerId), false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)]
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RequestLog))]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(ApiPathCrc32), nameof(ApiPathCrc32), false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(OwnerId), nameof(OwnerId), false)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)]
[Table( //
Name = $"{Chars.FLG_DB_TABLE_NAME_PREFIX}{nameof(Sys_RequestLog)}_{{yyyyMMdd}}"
, AsTable = $"{nameof(CreatedTime)}=2024-1-1(1 day)")]
public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFieldCreatedClientIp
{
/// <summary>
Expand Down Expand Up @@ -91,4 +93,12 @@ public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFi
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }

/// <summary>
/// 请求跟踪标识
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual Guid TraceId { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace NetAdmin.Domain.DbMaps.Sys;
/// 请求日志明细表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RequestLogDetail))]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)]
public record Sys_RequestLogDetail : SimpleEntity, IFieldCreatedTime, IFieldCreatedClientUserAgent
{
/// <inheritdoc />
Expand Down Expand Up @@ -121,12 +122,4 @@ public record Sys_RequestLogDetail : SimpleEntity, IFieldCreatedTime, IFieldCrea
[CsvIgnore]
[JsonIgnore]
public virtual int? ServerIp { get; init; }

/// <summary>
/// 请求跟踪标识
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)]
[CsvIgnore]
[JsonIgnore]
public virtual string TraceId { get; init; }
}
45 changes: 25 additions & 20 deletions src/backend/NetAdmin.Domain/Dto/Dependency/DynamicFilterInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,34 @@ private static void ProcessDynamicFilter(FreeSql.Internal.Model.DynamicFilterInf
}
}

if (d?.Operator != DynamicFilterOperator.DateRange) {
return;
if (new[] { nameof(IFieldCreatedClientIp.CreatedClientIp), nameof(IFieldModifiedClientIp.ModifiedClientIp) }
.Contains(d?.Field, StringComparer.OrdinalIgnoreCase)) {
var val = d!.Value?.ToString();
if (val?.IsIpV4() == true) {
d.Value = val.IpV4ToInt32();
}
}
else if (d?.Operator == DynamicFilterOperator.DateRange) {
var values = ((JsonElement)d.Value).Deserialize<string[]>();
if (!DateTime.TryParse(values[0], CultureInfo.InvariantCulture, out _)) {
var result = values[0]
.ExecuteCSharpCodeAsync<DateTime>([typeof(DateTime).Assembly], nameof(System))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
values[0] = $"{result:yyyy-MM-dd HH:mm:ss}";
}

var values = ((JsonElement)d.Value).Deserialize<string[]>();
if (!DateTime.TryParse(values[0], CultureInfo.InvariantCulture, out _)) {
var result = values[0]
.ExecuteCSharpCodeAsync<DateTime>([typeof(DateTime).Assembly], nameof(System))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
values[0] = $"{result:yyyy-MM-dd HH:mm:ss}";
}
if (!DateTime.TryParse(values[1], CultureInfo.InvariantCulture, out _)) {
var result = values[1]
.ExecuteCSharpCodeAsync<DateTime>([typeof(DateTime).Assembly], nameof(System))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
values[1] = $"{result:yyyy-MM-dd HH:mm:ss}";
}

if (!DateTime.TryParse(values[1], CultureInfo.InvariantCulture, out _)) {
var result = values[1]
.ExecuteCSharpCodeAsync<DateTime>([typeof(DateTime).Assembly], nameof(System))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
values[1] = $"{result:yyyy-MM-dd HH:mm:ss}";
d.Value = values;
}

d.Value = values;
}
}
Loading

0 comments on commit faaf5aa

Please sign in to comment.