fix: 🐛 操作日志不显示userName (#141)

[skip ci]

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
nsnail 2024-06-12 21:47:08 +08:00 committed by GitHub
parent a3ab97019d
commit 705d027da4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 216 additions and 222 deletions

View File

@ -23,7 +23,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.26.0.92422"> <PackageReference Include="SonarAnalyzer.CSharp" Version="9.27.0.93347">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@ -9,7 +9,7 @@
"packages": [ "packages": [
{ {
"packageName": "Furion.Pure.NS", "packageName": "Furion.Pure.NS",
"version": "4.9.3-ns1" "version": "4.9.4-ns1"
} }
] ]
} }

View File

@ -10,11 +10,6 @@ public interface IFieldCreatedClient
/// </summary> /// </summary>
int? CreatedClientIp { get; init; } int? CreatedClientIp { get; init; }
/// <summary>
/// 创建者来源地址
/// </summary>
string CreatedReferer { get; init; }
/// <summary> /// <summary>
/// 创建者客户端用户代理 /// 创建者客户端用户代理
/// </summary> /// </summary>

View File

@ -1,5 +1,16 @@
using NetAdmin.Domain.Attributes;
namespace NetAdmin.Domain.DbMaps.Dependency; namespace NetAdmin.Domain.DbMaps.Dependency;
/// <inheritdoc />
public abstract record SimpleEntity : SimpleEntity<long>
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[Column(IsIdentity = false, IsPrimary = true, Position = 1)]
[Snowflake]
public override long Id { get; init; }
}
/// <summary> /// <summary>
/// 简单实体 /// 简单实体
/// </summary> /// </summary>

View File

@ -31,7 +31,7 @@ public record Sys_JobRecord : LiteImmutableEntity
/// </summary> /// </summary>
[Column] [Column]
[JsonIgnore] [JsonIgnore]
public virtual int HttpStatusCode { get; init; } public int HttpStatusCode { get; init; }
/// <summary> /// <summary>
/// 拥有者信息 /// 拥有者信息

View File

@ -8,9 +8,10 @@ namespace NetAdmin.Domain.DbMaps.Sys;
/// </summary> /// </summary>
[Index(Chars.FLG_DB_INDEX_PREFIX + nameof(ApiId), nameof(ApiId), false)] [Index(Chars.FLG_DB_INDEX_PREFIX + nameof(ApiId), nameof(ApiId), false)]
[Index(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), nameof(CreatedTime), false)] [Index(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), nameof(CreatedTime), false)]
[Index(Chars.FLG_DB_INDEX_PREFIX + nameof(UserId), nameof(UserId), false)]
[Index(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)] [Index(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)]
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RequestLog))] [Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RequestLog))]
public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldCreatedClient
{ {
/// <summary> /// <summary>
/// 接口 /// 接口
@ -26,23 +27,17 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
[JsonIgnore] [JsonIgnore]
public virtual string ApiId { get; init; } public virtual string ApiId { get; init; }
/// <summary> /// <inheritdoc />
/// 创建者客户端IP
/// </summary>
[Column(Position = -1)] [Column(Position = -1)]
[JsonIgnore] [JsonIgnore]
public int? CreatedClientIp { get; init; } public int? CreatedClientIp { get; init; }
/// <summary> /// <inheritdoc />
/// 创建者来源地址 [Column(ServerTime = DateTimeKind.Local, CanUpdate = false, Position = -1)]
/// </summary>
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
[JsonIgnore] [JsonIgnore]
public string CreatedReferer { get; init; } public virtual DateTime CreatedTime { get; init; }
/// <summary> /// <inheritdoc />
/// 创建者客户端用户代理
/// </summary>
#if DBTYPE_SQLSERVER #if DBTYPE_SQLSERVER
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_1022)] [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_1022)]
#else #else
@ -76,17 +71,6 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
[JsonIgnore] [JsonIgnore]
public virtual string Exception { get; init; } public virtual string Exception { 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
[JsonIgnore]
public virtual string ExtraData { get; init; }
/// <summary> /// <summary>
/// HTTP状态码 /// HTTP状态码
/// </summary> /// </summary>
@ -101,13 +85,6 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
[JsonIgnore] [JsonIgnore]
public virtual string Method { get; init; } public virtual string Method { get; init; }
/// <summary>
/// 来源地址
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)]
[JsonIgnore]
public virtual string ReferUrl { get; init; }
/// <summary> /// <summary>
/// 请求内容 /// 请求内容
/// </summary> /// </summary>
@ -179,4 +156,18 @@ public record Sys_RequestLog : ImmutableEntity, IFieldCreatedClient
[Column] [Column]
[JsonIgnore] [JsonIgnore]
public virtual int? ServerIp { get; init; } public virtual int? ServerIp { get; init; }
/// <summary>
/// 用户
/// </summary>
[JsonIgnore]
[Navigate(nameof(UserId))]
public Sys_User User { get; init; }
/// <summary>
/// 用户编号
/// </summary>
[Column]
[JsonIgnore]
public virtual long? UserId { get; init; }
} }

View File

@ -1,5 +1,6 @@
using NetAdmin.Domain.DbMaps.Dependency.Fields; using NetAdmin.Domain.DbMaps.Dependency.Fields;
using NetAdmin.Domain.DbMaps.Sys; using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.User;
namespace NetAdmin.Domain.Dto.Sys.RequestLog; namespace NetAdmin.Domain.Dto.Sys.RequestLog;
@ -13,6 +14,11 @@ public sealed record QueryRequestLogRsp : Sys_RequestLog, IRegister
/// </summary> /// </summary>
public new string CreatedClientIp => base.CreatedClientIp?.ToIpV4(); public new string CreatedClientIp => base.CreatedClientIp?.ToIpV4();
/// <summary>
/// 登录名
/// </summary>
public string LoginName => RequestBody?.ToObject<LoginByPwdReq>()?.Account;
/// <summary> /// <summary>
/// 操作系统 /// 操作系统
/// </summary> /// </summary>
@ -35,10 +41,6 @@ public sealed record QueryRequestLogRsp : Sys_RequestLog, IRegister
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string CreatedUserAgent { get; init; } public override string CreatedUserAgent { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string CreatedUserName { get; init; }
/// <inheritdoc cref="Sys_RequestLog.Duration" /> /// <inheritdoc cref="Sys_RequestLog.Duration" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Duration { get; init; } public override long Duration { get; init; }
@ -51,10 +53,6 @@ public sealed record QueryRequestLogRsp : Sys_RequestLog, IRegister
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Exception { get; init; } public override string Exception { get; init; }
/// <inheritdoc cref="Sys_RequestLog.ExtraData" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ExtraData { get; init; }
/// <inheritdoc cref="Sys_RequestLog.HttpStatusCode" /> /// <inheritdoc cref="Sys_RequestLog.HttpStatusCode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int HttpStatusCode { get; init; } public override int HttpStatusCode { get; init; }
@ -63,10 +61,6 @@ public sealed record QueryRequestLogRsp : Sys_RequestLog, IRegister
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Method { get; init; } public override string Method { get; init; }
/// <inheritdoc cref="Sys_RequestLog.ReferUrl" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ReferUrl { get; init; }
/// <inheritdoc cref="Sys_RequestLog.RequestBody" /> /// <inheritdoc cref="Sys_RequestLog.RequestBody" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string RequestBody { get; init; } public override string RequestBody { get; init; }
@ -99,6 +93,13 @@ public sealed record QueryRequestLogRsp : Sys_RequestLog, IRegister
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override int? ServerIp { get; init; } public override int? ServerIp { get; init; }
/// <inheritdoc cref="Sys_RequestLog.User" />
public new QueryUserRsp User { get; init; }
/// <inheritdoc cref="Sys_RequestLog.UserId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? UserId { get; init; }
/// <inheritdoc /> /// <inheritdoc />
public void Register(TypeAdapterConfig config) public void Register(TypeAdapterConfig config)
{ {

View File

@ -7,17 +7,10 @@ namespace NetAdmin.Host.Utils;
/// <summary> /// <summary>
/// 请求日志记录器 /// 请求日志记录器
/// </summary> /// </summary>
public sealed class RequestLogger( public sealed class RequestLogger(ILogger<RequestLogger> logger, IEventPublisher eventPublisher) : ISingleton
ILogger<RequestLogger> logger
, IOptions<SpecificationDocumentSettingsOptions> specificationDocumentSettingsOptions
, IEventPublisher eventPublisher) : ISingleton
{ {
private static readonly string[] _textContentTypes = ["text", "json", "xml", "urlencoded"]; private static readonly string[] _textContentTypes = ["text", "json", "xml", "urlencoded"];
private readonly int _tokenPrefixLength
= specificationDocumentSettingsOptions?.Value.SecurityDefinitions?[0]?.Scheme?.Length + 1 ??
0; // eg. "Bearer ";
/// <summary> /// <summary>
/// 生成审计数据 /// 生成审计数据
/// </summary> /// </summary>
@ -30,7 +23,6 @@ public sealed class RequestLogger(
var auditData = new CreateRequestLogReq { var auditData = new CreateRequestLogReq {
Duration = duration Duration = duration
, Method = context.Request.Method , Method = context.Request.Method
, ReferUrl = context.Request.GetRefererUrlAddress()
, RequestContentType = context.Request.ContentType , RequestContentType = context.Request.ContentType
, RequestBody = Array.Exists( // , RequestBody = Array.Exists( //
_textContentTypes _textContentTypes
@ -50,8 +42,7 @@ public sealed class RequestLogger(
, HttpStatusCode = context.Response.StatusCode , HttpStatusCode = context.Response.StatusCode
, ErrorCode = errorCode , ErrorCode = errorCode
, Exception = exception?.Error.ToString() , Exception = exception?.Error.ToString()
, CreatedUserId = associatedUser?.UserId , UserId = associatedUser?.UserId
, CreatedUserName = associatedUser?.UserName
, CreatedUserAgent = context.Request.Headers.UserAgent.ToString() , CreatedUserAgent = context.Request.Headers.UserAgent.ToString()
, CreatedClientIp = context.GetRealIpAddress() , CreatedClientIp = context.GetRealIpAddress()
?.MapToIPv4() ?.MapToIPv4()
@ -77,7 +68,8 @@ public sealed class RequestLogger(
ContextUserToken userToken = null; ContextUserToken userToken = null;
try { try {
var jsonWebToken = JWTEncryption.ReadJwtToken(token[_tokenPrefixLength..]); var jsonWebToken
= JWTEncryption.ReadJwtToken(token.TrimStart($"{Chars.FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA} "));
var claim = jsonWebToken?.Claims.FirstOrDefault(y => y.Type == nameof(ContextUserToken)); var claim = jsonWebToken?.Claims.FirstOrDefault(y => y.Type == nameof(ContextUserToken));
userToken = claim?.Value.ToObject<ContextUserToken>(); userToken = claim?.Value.ToObject<ContextUserToken>();
} }

View File

@ -63,13 +63,6 @@ public sealed class SqlAuditor : ISingleton
} }
} }
private static void SetCreatedReferer(AuditValueEventArgs e)
{
if (e.Value is null or "") {
e.Value = App.HttpContext?.Request.GetRefererUrlAddress();
}
}
private static void SetCreatedTime(AuditValueEventArgs e) private static void SetCreatedTime(AuditValueEventArgs e)
{ {
if (e.Value == null || (e.Value is DateTime val && val == default)) { if (e.Value == null || (e.Value is DateTime val && val == default)) {
@ -119,9 +112,6 @@ public sealed class SqlAuditor : ISingleton
case nameof(IFieldCreatedClient.CreatedUserAgent): case nameof(IFieldCreatedClient.CreatedUserAgent):
SetCreatedUserAgent(e); SetCreatedUserAgent(e);
break; break;
case nameof(IFieldCreatedClient.CreatedReferer):
SetCreatedReferer(e);
break;
default: default:
return; return;
} }

View File

@ -67,6 +67,7 @@ public static class Chars
public const string FLG_HTTP_METHOD_POST = "POST"; public const string FLG_HTTP_METHOD_POST = "POST";
public const string FLG_HTTP_METHOD_PUT = "PUT"; public const string FLG_HTTP_METHOD_PUT = "PUT";
public const string FLG_HTTP_METHOD_TRACE = "TRACE"; public const string FLG_HTTP_METHOD_TRACE = "TRACE";
public const string FLG_PATH_API_SYS_USER_LOGIN_BY_PWD = "api/sys/user/login.by.pwd";
public const string FLG_PATH_PREFIX_HEALTH_CHECK = "probe/health.check"; public const string FLG_PATH_PREFIX_HEALTH_CHECK = "probe/health.check";
public const string FLG_RANDOM_UNAME_PWD = "VcXlp7WY"; public const string FLG_RANDOM_UNAME_PWD = "VcXlp7WY";
public const string FLG_REDIS_INSTANCE_DATA_CACHE = "DataCache"; public const string FLG_REDIS_INSTANCE_DATA_CACHE = "DataCache";

View File

@ -9,9 +9,9 @@
<PackageReference Include="Cronos" Version="0.8.4"/> <PackageReference Include="Cronos" Version="0.8.4"/>
<PackageReference Include="FreeSql.DbContext.NS" Version="3.2.821-ns1"/> <PackageReference Include="FreeSql.DbContext.NS" Version="3.2.821-ns1"/>
<PackageReference Include="FreeSql.Provider.Sqlite.NS" Version="3.2.821-ns1"/> <PackageReference Include="FreeSql.Provider.Sqlite.NS" Version="3.2.821-ns1"/>
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.3"/> <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.4"/>
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster.NS" Version="4.9.3-ns1"/> <PackageReference Include="Furion.Extras.ObjectMapper.Mapster.NS" Version="4.9.4-ns1"/>
<PackageReference Include="Furion.Pure.NS" Version="4.9.3-ns1"/> <PackageReference Include="Furion.Pure.NS" Version="4.9.4-ns1"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0-preview.4.24267.6"/> <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0-preview.4.24267.6"/>
<PackageReference Include="Minio" Version="6.0.2"/> <PackageReference Include="Minio" Version="6.0.2"/>
<PackageReference Include="NSExt" Version="2.1.0"/> <PackageReference Include="NSExt" Version="2.1.0"/>

View File

@ -12,6 +12,8 @@ namespace NetAdmin.SysComponent.Application.Services.Sys;
public sealed class RequestLogService(BasicRepository<Sys_RequestLog, long> rpo) // public sealed class RequestLogService(BasicRepository<Sys_RequestLog, long> rpo) //
: RepositoryService<Sys_RequestLog, long, IRequestLogService>(rpo), IRequestLogService : RepositoryService<Sys_RequestLog, long, IRequestLogService>(rpo), IRequestLogService
{ {
private static readonly Regex _regex = new(Chars.RGXL_IP_V4);
/// <inheritdoc /> /// <inheritdoc />
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req) public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{ {
@ -134,24 +136,43 @@ public sealed class RequestLogService(BasicRepository<Sys_RequestLog, long> rpo)
public async Task<PagedQueryRsp<QueryRequestLogRsp>> PagedQueryAsync(PagedQueryReq<QueryRequestLogReq> req) public async Task<PagedQueryRsp<QueryRequestLogRsp>> PagedQueryAsync(PagedQueryReq<QueryRequestLogReq> req)
{ {
req.ThrowIfInvalid(); req.ThrowIfInvalid();
var list = await QueryInternal(req) var select = QueryInternal(req)
.Page(req.Page, req.PageSize) .Page(req.Page, req.PageSize)
#if DBTYPE_SQLSERVER #if DBTYPE_SQLSERVER
.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait)
#endif #endif
.Count(out var total) .Count(out var total);
.ToListAsync(a => new { object list = req.DynamicFilter?.Filters?.Exists(
x => nameof(QueryRequestLogReq.ApiId).Equals(x.Field, StringComparison.OrdinalIgnoreCase) &&
Chars.FLG_PATH_API_SYS_USER_LOGIN_BY_PWD.Equals( //
x.Value.ToString(), StringComparison.OrdinalIgnoreCase)) ?? false
? await select.ToListAsync(a => new {
a.ApiId a.ApiId
, ApiSummary = a.Api.Summary , ApiSummary = a.Api.Summary
, a.ExtraData
, a.CreatedClientIp , a.CreatedClientIp
, a.CreatedTime , a.CreatedTime
, a.CreatedUserName
, a.Duration , a.Duration
, a.Method , a.Method
, a.CreatedUserAgent , a.CreatedUserAgent
, a.HttpStatusCode , a.HttpStatusCode
, a.Id , a.Id
, a.UserId
, a.User
, a.RequestBody
})
.ConfigureAwait(false)
: await select.ToListAsync(a => new {
a.ApiId
, ApiSummary = a.Api.Summary
, a.CreatedClientIp
, a.CreatedTime
, a.Duration
, a.Method
, a.CreatedUserAgent
, a.HttpStatusCode
, a.Id
, a.UserId
, a.User
}) })
.ConfigureAwait(false); .ConfigureAwait(false);
@ -175,7 +196,18 @@ public sealed class RequestLogService(BasicRepository<Sys_RequestLog, long> rpo)
private ISelect<Sys_RequestLog> QueryInternal(QueryReq<QueryRequestLogReq> req) private ISelect<Sys_RequestLog> QueryInternal(QueryReq<QueryRequestLogReq> req)
{ {
var ret = Rpo.Select.Include(a => a.Api).WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); var ret = Rpo.Select.Include(a => a.Api).Include(a => a.User).WhereDynamicFilter(req.DynamicFilter);
if (req.Filter?.Id is not 0) {
ret = ret.WhereDynamic(req.Filter);
}
if (req.Keywords?.Length > 0) {
ret = _regex.IsMatch(req.Keywords)
? ret.Where(a => a.CreatedClientIp == req.Keywords.IpV4ToInt32())
: ret.Where(a => a.Id == req.Keywords.Int64Try(0) || a.UserId == req.Keywords.Int64Try(0) ||
a.User.UserName == req.Keywords || a.RequestBody.Contains(req.Keywords));
}
switch (req.Order) { switch (req.Order) {
case Orders.None: case Orders.None:
return ret; return ret;

View File

@ -1,5 +1,3 @@
using NetAdmin.Domain.Dto.Sys.RequestLog;
using NetAdmin.Domain.Dto.Sys.User;
using NetAdmin.Domain.Events.Sys; using NetAdmin.Domain.Events.Sys;
using NetAdmin.SysComponent.Application.Services.Sys.Dependency; using NetAdmin.SysComponent.Application.Services.Sys.Dependency;
@ -27,22 +25,7 @@ public sealed class OperationLogger : IEventSubscriber
return; return;
} }
CreateRequestLogReq logReq = null; operationEvent.Data.TruncateStrings();
_ = await App.GetService<IRequestLogService>().CreateAsync(operationEvent.Data).ConfigureAwait(false);
// 登录日志特殊处理
if (operationEvent.Data.ApiId.Equals("api/sys/user/login.by.pwd", StringComparison.OrdinalIgnoreCase)) {
try {
var loginReq = operationEvent.Data.RequestBody.ToObject<LoginByPwdReq>();
logReq = operationEvent.Data with { ExtraData = loginReq.Account };
}
catch {
// ignored
}
}
logReq ??= operationEvent.Data;
var logService = App.GetService<IRequestLogService>();
logReq.TruncateStrings();
_ = await logService.CreateAsync(logReq).ConfigureAwait(false);
} }
} }

View File

@ -16,8 +16,6 @@ public abstract class WebApiTestBase<T>(WebApplicationFactory<T> factory, ITestO
where T : AppStartup where T : AppStartup
{ {
private const string _ACCOUNT = "root"; private const string _ACCOUNT = "root";
private const string _API_SYS_USER_LOGIN_BY_PWD = "/api/sys/user/login.by.pwd";
private const string _AUTH_SCHEMA = "Bearer";
private const string _PASSWORD = "1234qwer"; private const string _PASSWORD = "1234qwer";
private string _accessToken; private string _accessToken;
@ -28,7 +26,7 @@ public abstract class WebApiTestBase<T>(WebApplicationFactory<T> factory, ITestO
{ {
var client = factory.CreateClient(); var client = factory.CreateClient();
if (_accessToken == null) { if (_accessToken == null) {
var loginRsp = await client.PostAsync(_API_SYS_USER_LOGIN_BY_PWD var loginRsp = await client.PostAsync(Chars.FLG_PATH_API_SYS_USER_LOGIN_BY_PWD
, JsonContent.Create( , JsonContent.Create(
new LoginByPwdReq { Password = _PASSWORD, Account = _ACCOUNT })) new LoginByPwdReq { Password = _PASSWORD, Account = _ACCOUNT }))
.ConfigureAwait(false); .ConfigureAwait(false);
@ -37,7 +35,8 @@ public abstract class WebApiTestBase<T>(WebApplicationFactory<T> factory, ITestO
_accessToken = loginRspObj.Data.AccessToken; _accessToken = loginRspObj.Data.AccessToken;
} }
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(_AUTH_SCHEMA, _accessToken); client.DefaultRequestHeaders.Authorization
= new AuthenticationHeaderValue(Chars.FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA, _accessToken);
var ret = await client.PostAsync(url, content).ConfigureAwait(false); var ret = await client.PostAsync(url, content).ConfigureAwait(false);
testOutputHelper.WriteLine(await ret.Content.ReadAsStringAsync().ConfigureAwait(false)); testOutputHelper.WriteLine(await ret.Content.ReadAsStringAsync().ConfigureAwait(false));
return ret; return ret;

View File

@ -130,11 +130,14 @@ export default {
<style scoped> <style scoped>
.sc-select-filter { .sc-select-filter {
width: 100%; width: 100%;
display: flex;
flex-wrap: wrap;
} }
.sc-select-filter__item { .sc-select-filter__item {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
width: 50%;
} }
.sc-select-filter__item-title { .sc-select-filter__item-title {

View File

@ -15,7 +15,7 @@
<el-icon-full-screen /> <el-icon-full-screen />
</el-icon> </el-icon>
</div> </div>
<div @click="tasks" class="tasks panel-item"> <div v-auth="'sys/job/userbar'" @click="tasks" class="tasks panel-item">
<el-icon> <el-icon>
<sc-icon-ScheduledJob /> <sc-icon-ScheduledJob />
</el-icon> </el-icon>

View File

@ -47,6 +47,9 @@ export default {
global.enums = preloads[2]?.data global.enums = preloads[2]?.data
global.numbers = preloads[3]?.data global.numbers = preloads[3]?.data
global.chars = preloads[4]?.data global.chars = preloads[4]?.data
global.permissions = tool.recursiveFindProperty(preloads[0]?.data, 'type', 'button').map((x) => x.tag) global.permissions =
global.user?.roles.findIndex((x) => x.ignorePermissionControl) >= 0
? ['*/*/*']
: tool.recursiveFindProperty(preloads[0]?.data, 'type', 'button').map((x) => x.tag)
}, },
} }

View File

@ -23,21 +23,9 @@
:controls="[ :controls="[
{ {
type: 'input', type: 'input',
field: ['dy', 'id'], field: ['root', 'keywords'],
placeholder: '日志编号', placeholder: '日志编号 / 登录名 / 客户端IP',
style: 'width:15rem', style: 'width:25rem',
},
{
type: 'input',
field: ['dy', 'extraData'],
placeholder: '登录名',
style: 'width:15rem',
},
{
type: 'input',
field: ['dy', 'createdClientIp'],
placeholder: '客户端IP',
style: 'width:15rem',
}, },
]" ]"
:vue="this" :vue="this"
@ -54,7 +42,7 @@
<sc-table <sc-table
v-loading="loading" v-loading="loading"
:apiObj="$API.sys_log.pagedQuery" :apiObj="$API.sys_log.pagedQuery"
:context-menus="['id', 'httpStatusCode', 'extraData', 'createdClientIp', 'os', 'createdUserAgent', 'createdTime']" :context-menus="['id', 'httpStatusCode', 'createdClientIp', 'createdUserAgent', 'createdTime']"
:context-opers="['view']" :context-opers="['view']"
:default-sort="{ prop: 'createdTime', order: 'descending' }" :default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query" :params="query"
@ -73,13 +61,13 @@
{{ scope.row.httpStatusCode === 200 ? '成功' : '失败' }} {{ scope.row.httpStatusCode === 200 ? '成功' : '失败' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('登录名')" prop="extraData" sortable="custom" width="200" /> <el-table-column :label="$t('登录名')" prop="loginName" width="150" />
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" sortable="custom" width="200"> <el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
<template #default="scope"> <template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip> <na-ip :ip="scope.row.createdClientIp"></na-ip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('操作系统')" prop="os" width="150" /> <el-table-column :label="$t('操作系统')" align="center" prop="os" width="150" />
<el-table-column :label="$t('用户代理')" prop="createdUserAgent" show-overflow-tooltip sortable="custom" /> <el-table-column :label="$t('用户代理')" prop="createdUserAgent" show-overflow-tooltip sortable="custom" />
</sc-table> </sc-table>
</el-main> </el-main>
@ -120,7 +108,11 @@ export default {
}, },
} }
}, },
mounted() { async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.apiId = 'api/sys/user/login.by.pwd' this.$refs.search.form.dy.apiId = 'api/sys/user/login.by.pwd'
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [ this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`, `${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
@ -128,6 +120,9 @@ export default {
] ]
}, },
created() { created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
this.query.dynamicFilter.filters.push({ this.query.dynamicFilter.filters.push({
field: 'apiId', field: 'apiId',
operator: 'eq', operator: 'eq',
@ -157,41 +152,6 @@ export default {
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')), value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
}) })
} }
if (form.dy.httpStatusCode) {
this.query.dynamicFilter.filters.push({
field: 'httpStatusCode',
operator: 'range',
value: form.dy.httpStatusCode,
})
}
if (form.dy.apiId) {
this.query.dynamicFilter.filters.push({
field: 'apiId',
operator: 'contains',
value: form.dy.apiId,
})
}
if (form.dy.id) {
this.query.dynamicFilter.filters.push({
field: 'id',
operator: 'eq',
value: form.dy.id,
})
}
if (form.dy.extraData) {
this.query.dynamicFilter.filters.push({
field: 'extraData',
operator: 'contains',
value: form.dy.extraData,
})
}
if (form.dy.createdClientIp) {
this.query.dynamicFilter.filters.push({
field: 'createdClientIp',
operator: 'contains',
value: form.dy.createdClientIp,
})
}
if (typeof form.dy.loginResult === 'boolean') { if (typeof form.dy.loginResult === 'boolean') {
this.query.dynamicFilter.filters.push( this.query.dynamicFilter.filters.push(
form.dy.loginResult form.dy.loginResult
@ -207,7 +167,6 @@ export default {
}, },
) )
} }
this.$refs.table.upData() this.$refs.table.upData()
}, },

View File

@ -1,15 +1,26 @@
<template> <template>
<el-container> <el-container>
<el-header style="height: auto; padding: 0 1rem">
<sc-select-filter
:data="[
{
title: $t('操作结果'),
key: 'operationResult',
options: [
{ label: $t('全部'), value: '' },
{ label: $t('成功'), value: true },
{ label: $t('失败'), value: false },
],
},
]"
:label-width="6"
@on-change="filterChange"
ref="selectFilter"></sc-select-filter>
</el-header>
<el-header> <el-header>
<div class="left-panel"> <div class="left-panel">
<na-search <na-search
:controls="[ :controls="[
{
type: 'input',
field: ['filter', 'id'],
placeholder: '日志编号',
style: 'width:12rem',
},
{ {
multiple: true, multiple: true,
type: 'select', type: 'select',
@ -34,18 +45,13 @@
}, },
{ {
type: 'input', type: 'input',
field: ['dy', 'createdUserName'], field: ['root', 'keywords'],
placeholder: '用户名', placeholder: '日志编号 / 用户 / 客户端IP',
style: 'width:12rem', style: 'width:25rem',
},
{
type: 'input',
field: ['dy', 'createdClientIp'],
placeholder: '客户端IP',
style: 'width:12rem',
}, },
]" ]"
:vue="this" :vue="this"
@reset="Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = ['']))"
@search="onSearch" @search="onSearch"
dateFormat="YYYY-MM-DD HH:mm:ss" dateFormat="YYYY-MM-DD HH:mm:ss"
dateType="datetimerange" dateType="datetimerange"
@ -57,19 +63,18 @@
<el-main class="nopadding"> <el-main class="nopadding">
<sc-table <sc-table
:apiObj="$API.sys_log.pagedQuery" :apiObj="$API.sys_log.pagedQuery"
:context-menus="['id', 'httpStatusCode', 'apiId', 'createdUserName', 'method', 'duration', 'createdClientIp', 'os', 'createdTime']" :context-menus="['id', 'httpStatusCode', 'apiId', 'userId', 'method', 'duration', 'createdClientIp', 'createdTime']"
:context-opers="[]" :context-opers="[]"
:default-sort="{ prop: 'createdTime', order: 'descending' }" :default-sort="{ prop: 'createdTime', order: 'descending' }"
:params="query" :params="query"
:vue="this" :vue="this"
@row-click="rowClick"
ref="table" ref="table"
remoteFilter remote-filter
remoteSort remote-sort
row-key="id" row-key="id"
stripe> stripe>
<el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150"> </el-table-column <el-table-column :label="$t('日志编号')" prop="id" sortable="custom" width="150" />
><el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" /> <el-table-column :label="$t('创建时间')" prop="createdTime" sortable="custom" width="170" />
<el-table-column :label="$t('响应码')" align="center" prop="httpStatusCode" sortable="custom" width="100"> <el-table-column :label="$t('响应码')" align="center" prop="httpStatusCode" sortable="custom" width="100">
<template #default="{ row }"> <template #default="{ row }">
<sc-status-indicator :type="row.httpStatusCode >= 200 && row.httpStatusCode < 300 ? 'success' : 'danger'" /> <sc-status-indicator :type="row.httpStatusCode >= 200 && row.httpStatusCode < 300 ? 'success' : 'danger'" />
@ -96,20 +101,33 @@
align="right" align="right"
prop="duration" prop="duration"
sortable="custom" sortable="custom"
width="120"> width="90">
</el-table-column> </el-table-column>
</el-table-column> </el-table-column>
<el-table-column :label="$t('用户名')" prop="createdUserName" sortable="custom" width="150"> <na-col-user
<template #default="scope"> v-auth="'sys/log/operation/user'"
{{ scope.row.apiId === 'api/sys/user/pwd.login' ? scope.row.extraData : scope.row.createdUserName }} header-align="center"
</template> label="用户"
</el-table-column> nestProp="user.userName"
nestProp2="user.id"
prop="userId"
sortable="custom"
width="170"></na-col-user>
<el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200"> <el-table-column :label="$t('客户端IP')" prop="createdClientIp" show-overflow-tooltip sortable="custom" width="200">
<template #default="scope"> <template #default="scope">
<na-ip :ip="scope.row.createdClientIp"></na-ip> <na-ip :ip="scope.row.createdClientIp"></na-ip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('操作系统')" align="center" prop="os" width="150" /> <el-table-column :label="$t('操作系统')" align="center" prop="os" width="150" />
<na-col-operation
:buttons="[
{
icon: 'el-icon-view',
click: rowClick,
},
]"
:vue="this"
width="70" />
</sc-table> </sc-table>
</el-main> </el-main>
</el-container> </el-container>
@ -124,7 +142,6 @@ import ScTable from '@/components/scTable/index.vue'
import ScStatusIndicator from '@/components/scMini/scStatusIndicator.vue' import ScStatusIndicator from '@/components/scMini/scStatusIndicator.vue'
export default { export default {
inject: ['reload'],
computed: { computed: {
tool() { tool() {
return tool return tool
@ -136,6 +153,7 @@ export default {
naInfo, naInfo,
}, },
watch: {}, watch: {},
inject: ['reload'],
data() { data() {
return { return {
query: { query: {
@ -155,13 +173,28 @@ export default {
}, },
} }
}, },
mounted() { async mounted() {
if (this.keywords) {
this.$refs.search.form.root.keywords = this.keywords
this.$refs.search.keepKeywords = this.keywords
}
this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [ this.$refs.search.form.dy.createdTime = this.$refs.search.keepCreatedTime = [
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`, `${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
`${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`, `${tool.dateFormat(new Date(), 'yyyy-MM-dd')} 00:00:00`,
] ]
}, },
created() {
if (this.keywords) {
this.query.keywords = this.keywords
}
},
methods: { methods: {
filterChange(data) {
Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
})
this.$refs.search.search()
},
// //
onSearch(form) { onSearch(form) {
if (Array.isArray(form.dy.createdTime)) { if (Array.isArray(form.dy.createdTime)) {
@ -186,19 +219,20 @@ export default {
value: form.dy.apiId, value: form.dy.apiId,
}) })
} }
if (typeof form.dy.createdUserName === 'string' && form.dy.createdUserName.trim() !== '') { if (typeof form.dy.operationResult === 'boolean') {
this.query.dynamicFilter.filters.push({ this.query.dynamicFilter.filters.push(
field: 'createdUserName', form.dy.operationResult
operator: 'eq', ? {
value: form.dy.createdUserName, field: 'httpStatusCode',
}) operator: 'range',
value: '200,299',
} }
if (typeof form.dy.createdClientIp === 'string' && form.dy.createdClientIp.trim() !== '') { : {
this.query.dynamicFilter.filters.push({ field: 'httpStatusCode',
field: 'createdClientIp', operator: 'range',
operator: 'eq', value: '300,999',
value: form.dy.createdClientIp, },
}) )
} }
this.$refs.table.upData() this.$refs.table.upData()
}, },