refactor: ♻️ 批量查询ip归属地 (#164)

Co-authored-by: tk <fiyne1a@dingtalk.com>
This commit is contained in:
nsnail 2024-07-29 11:35:26 +08:00 committed by GitHub
parent 7c56c8d571
commit 2b4c25c07c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 131 additions and 73 deletions

View File

@ -38,7 +38,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
[Column] [Column]
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual int Duration { get; init; } public virtual int Duration { get; protected init; }
/// <summary> /// <summary>
/// 程序响应码 /// 程序响应码
@ -46,7 +46,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
[Column] [Column]
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual ErrorCodes ErrorCode { get; init; } public virtual ErrorCodes ErrorCode { get; protected init; }
/// <summary> /// <summary>
/// HTTP状态码 /// HTTP状态码
@ -62,7 +62,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
[Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)]
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual string LoginUserName { get; init; } public virtual string LoginUserName { get; protected init; }
/// <summary> /// <summary>
/// 拥有者 /// 拥有者
@ -94,7 +94,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
#endif #endif
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual string RequestBody { get; init; } public virtual string RequestBody { get; protected init; }
/// <summary> /// <summary>
/// 请求头信息 /// 请求头信息
@ -106,7 +106,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
#endif #endif
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual string RequestHeaders { get; init; } public virtual string RequestHeaders { get; protected init; }
/// <summary> /// <summary>
/// 请求地址 /// 请求地址
@ -114,7 +114,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)]
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual string RequestUrl { get; init; } public virtual string RequestUrl { get; protected init; }
/// <summary> /// <summary>
/// 响应内容 /// 响应内容
@ -126,7 +126,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
#endif #endif
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual string ResponseBody { get; init; } public virtual string ResponseBody { get; protected init; }
/// <summary> /// <summary>
/// 响应头 /// 响应头
@ -138,7 +138,7 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
#endif #endif
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual string ResponseHeaders { get; init; } public virtual string ResponseHeaders { get; protected init; }
/// <summary> /// <summary>
/// 服务器IP /// 服务器IP
@ -146,5 +146,5 @@ public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFiel
[Column] [Column]
[CsvIgnore] [CsvIgnore]
[JsonIgnore] [JsonIgnore]
public virtual int? ServerIp { get; init; } public virtual int? ServerIp { get; protected init; }
} }

View File

@ -42,22 +42,8 @@ public sealed record DynamicFilterInfo : DataAbstraction
return ret; return ret;
} }
private static void ProcessDynamicFilter(FreeSql.Internal.Model.DynamicFilterInfo d) private static void ParseDateExp(FreeSql.Internal.Model.DynamicFilterInfo d)
{ {
if (d?.Filters != null) {
foreach (var filterInfo in d.Filters) {
ProcessDynamicFilter(filterInfo);
}
}
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[]>(); var values = ((JsonElement)d.Value).Deserialize<string[]>();
if (!DateTime.TryParse(values[0], CultureInfo.InvariantCulture, out _)) { if (!DateTime.TryParse(values[0], CultureInfo.InvariantCulture, out _)) {
var result = values[0] var result = values[0]
@ -79,5 +65,24 @@ public sealed record DynamicFilterInfo : DataAbstraction
d.Value = values; d.Value = values;
} }
private static void ProcessDynamicFilter(FreeSql.Internal.Model.DynamicFilterInfo d)
{
if (d?.Filters != null) {
foreach (var filterInfo in d.Filters) {
ProcessDynamicFilter(filterInfo);
}
}
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) {
ParseDateExp(d);
}
} }
} }

View File

@ -47,7 +47,7 @@ public sealed record ExportLoginLogRsp : QueryLoginLogRsp
[CsvIgnore(false)] [CsvIgnore(false)]
[CsvIndex(2)] [CsvIndex(2)]
[CsvName(nameof(Ln.登录名))] [CsvName(nameof(Ln.登录名))]
public override string LoginUserName { get; init; } public override string LoginUserName { get; protected init; }
/// <inheritdoc /> /// <inheritdoc />
[CsvIgnore] [CsvIgnore]

View File

@ -29,11 +29,11 @@ public record QueryLoginLogRsp : Sys_LoginLog
/// <inheritdoc cref="Sys_LoginLog.Duration" /> /// <inheritdoc cref="Sys_LoginLog.Duration" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override int Duration { get; init; } public override int Duration { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.ErrorCode" /> /// <inheritdoc cref="Sys_LoginLog.ErrorCode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override ErrorCodes ErrorCode { get; init; } public override ErrorCodes ErrorCode { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.HttpStatusCode" /> /// <inheritdoc cref="Sys_LoginLog.HttpStatusCode" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)] [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
@ -45,7 +45,7 @@ public record QueryLoginLogRsp : Sys_LoginLog
/// <inheritdoc cref="Sys_LoginLog.LoginUserName" /> /// <inheritdoc cref="Sys_LoginLog.LoginUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string LoginUserName { get; init; } public override string LoginUserName { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.Owner" /> /// <inheritdoc cref="Sys_LoginLog.Owner" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
@ -53,25 +53,25 @@ public record QueryLoginLogRsp : Sys_LoginLog
/// <inheritdoc cref="Sys_LoginLog.RequestBody" /> /// <inheritdoc cref="Sys_LoginLog.RequestBody" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string RequestBody { get; init; } public override string RequestBody { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.RequestHeaders" /> /// <inheritdoc cref="Sys_LoginLog.RequestHeaders" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string RequestHeaders { get; init; } public override string RequestHeaders { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.RequestUrl" /> /// <inheritdoc cref="Sys_LoginLog.RequestUrl" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string RequestUrl { get; init; } public override string RequestUrl { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.ResponseBody" /> /// <inheritdoc cref="Sys_LoginLog.ResponseBody" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ResponseBody { get; init; } public override string ResponseBody { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.ResponseHeaders" /> /// <inheritdoc cref="Sys_LoginLog.ResponseHeaders" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ResponseHeaders { get; init; } public override string ResponseHeaders { get; protected init; }
/// <inheritdoc cref="Sys_LoginLog.ServerIp" /> /// <inheritdoc cref="Sys_LoginLog.ServerIp" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override int? ServerIp { get; init; } public override int? ServerIp { get; protected init; }
} }

View File

@ -18,13 +18,12 @@ public sealed class ToolsService : ServiceBase<IToolsService>, IToolsService
/// <inheritdoc /> /// <inheritdoc />
public Task<IEnumerable<GetModulesRsp>> GetModulesAsync() public Task<IEnumerable<GetModulesRsp>> GetModulesAsync()
{ {
return Task.FromResult<IEnumerable<GetModulesRsp>>(AppDomain.CurrentDomain.GetAssemblies() return Task.FromResult<IEnumerable<GetModulesRsp>>( //
AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.FullName?.Contains('#') != true && a.FullName?.Contains("DynamicMethods") != true)
.Select(x => { .Select(x => {
var asm = x.GetName(); var asm = x.GetName();
return new GetModulesRsp { return new GetModulesRsp { Name = asm.Name, Version = asm.Version?.ToString() };
Name = asm.Name
, Version = asm.Version?.ToString()
};
}) })
.OrderBy(x => x.Name)); .OrderBy(x => x.Name));
} }

View File

@ -1,11 +1,32 @@
<template> <template>
<el-drawer v-model="visible" :size="size" :title="title" @closed="$emit('closed')" destroy-on-close> <el-drawer v-model="visible" :size="size" :title="title" @closed="$emit('closed')" destroy-on-close>
<el-main> <el-main v-loading="!data" style="height: 100%">
<el-descriptions :column="1" border class="font-monospace" size="small"> <el-descriptions v-if="data" :column="1" border class="font-monospace" size="small">
<el-descriptions-item v-for="(item, i) in data" :key="i" :label="i" label-class-name="w15"> <el-descriptions-item v-for="(item, i) in data" :key="i" :label="i" label-class-name="w15">
{{ item }} {{ item }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-empty v-if="esData && !esData.hits" v-loading="true"></el-empty>
<el-descriptions
v-for="(desc, i) in esData.hits.hits.map((x) => x._source)"
v-if="esData?.hits?.hits?.length > 0"
:column="1"
:key="i"
class="trace-log mt-8"
size="small">
<el-descriptions-item
v-for="(item, j) in Object.entries(desc)
.filter((x) => ['@timestamp', 'log_level', 'log_thread', 'log_source', 'log_message'].includes(x[0]))
.sort()"
:key="j"
:label="item[0]"
label-class-name="w15">
<span v-if="item[0] === '@timestamp'">
{{ $TOOL.dateFormat(item[1]) }}
</span>
<span v-else v-html="$TOOL.highLightKeywords($TOOL.unicodeDecode(item[1].toString()))" />
</el-descriptions-item>
</el-descriptions>
</el-main> </el-main>
</el-drawer> </el-drawer>
</template> </template>
@ -18,16 +39,27 @@ export default {
}, },
data() { data() {
return { return {
title: '', title: null,
visible: false, visible: false,
data: {}, esData: null,
data: null,
} }
}, },
methods: { methods: {
open(data, title) { async open(title, query, queryEs) {
this.title = title this.data = null
this.data = data this.title = null
this.esData = null
this.visible = true this.visible = true
const res = await query()
this.title = title(res.data)
this.data = this.$TOOL.sortProperties(res.data)
if (queryEs) {
this.esData = {}
this.$API.adm_tools.queryEsLog.post(queryEs).then((res) => {
this.esData = res.data
})
}
return this return this
}, },
}, },

View File

@ -57,7 +57,6 @@ import naColUser from '@/components/naColUser/index.vue'
import naDept from '@/components/naDept/index.vue' import naDept from '@/components/naDept/index.vue'
import naDicCatalog from '@/components/naDicCatalog/index.vue' import naDicCatalog from '@/components/naDicCatalog/index.vue'
import naFormEmail from '@/components/naFormEmail/index.vue' import naFormEmail from '@/components/naFormEmail/index.vue'
import naIp from '@/components/naIp/index.vue'
import naSearch from '@/components/naSearch' import naSearch from '@/components/naSearch'
import naUserSelect from '@/components/naUserSelect/index.vue' import naUserSelect from '@/components/naUserSelect/index.vue'
@ -97,7 +96,6 @@ export default {
app.component('naDept', naDept) app.component('naDept', naDept)
app.component('naDicCatalog', naDicCatalog) app.component('naDicCatalog', naDicCatalog)
app.component('naFormEmail', naFormEmail) app.component('naFormEmail', naFormEmail)
app.component('naIp', naIp)
app.component('naSearch', naSearch) app.component('naSearch', naSearch)
app.component('naUserSelect', naUserSelect) app.component('naUserSelect', naUserSelect)

View File

@ -121,12 +121,12 @@ export default {
this.getData() this.getData()
}, },
async rowClick(row) { async rowClick(row) {
this.loading = true
const res = await this.$API.sys_cache.getEntry.post({ key: row.key })
this.dialog.info = true this.dialog.info = true
await this.$nextTick() await this.$nextTick()
this.$refs.info.open(this.$TOOL.sortProperties(res.data), this.$t('缓存详情')) this.$refs.info.open(
this.loading = false () => this.$t('缓存详情'),
() => this.$API.sys_cache.getEntry.post({ key: row.key }),
)
}, },
async getData() { async getData() {
this.loading = true this.loading = true

View File

@ -48,6 +48,7 @@
:params="query" :params="query"
:query-api="$API.sys_loginlog.pagedQuery" :query-api="$API.sys_loginlog.pagedQuery"
:vue="this" :vue="this"
@data-change="dataChange"
ref="table" ref="table"
remote-filter remote-filter
remote-sort remote-sort
@ -63,7 +64,8 @@
<el-table-column :label="$t('登录名')" prop="loginUserName" sortable="custom" width="150" /> <el-table-column :label="$t('登录名')" prop="loginUserName" sortable="custom" width="150" />
<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="{ row }"> <template #default="{ row }">
<na-ip :ip="row.createdClientIp"></na-ip> <p>{{ row.createdClientIp }}</p>
<p>{{ this.ips.filter((x) => x.ip === row.createdClientIp)[0]?.region ?? '...' }}</p>
</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" />
@ -86,6 +88,7 @@
<script> <script>
import naInfo from '@/components/naInfo/index.vue' import naInfo from '@/components/naInfo/index.vue'
import http from '@/utils/request'
export default { export default {
components: { components: {
@ -98,6 +101,7 @@ export default {
dialog: { dialog: {
info: false, info: false,
}, },
ips: [],
loading: false, loading: false,
query: { query: {
dynamicFilter: { dynamicFilter: {
@ -111,6 +115,14 @@ export default {
}, },
inject: ['reload'], inject: ['reload'],
methods: { methods: {
async dataChange(data) {
this.apis = []
const ips = data.data.rows?.map((x) => x.createdClientIp)
const res = await Promise.all([
ips && ips.length > 0 ? http.get(`http://ip.line92.xyz/?ip=${ips.join('&ip=')}`) : new Promise((x) => x({ data: [] })),
])
this.ips = res[0]
},
filterChange(data) { filterChange(data) {
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {
this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value this.$refs.search.form.dy[key] = value === 'true' ? true : value === 'false' ? false : value
@ -151,10 +163,13 @@ export default {
async rowClick(row) { async rowClick(row) {
this.dialog.info = true this.dialog.info = true
await this.$nextTick() await this.$nextTick()
const res = await this.$API.sys_loginlog.get.post({ await this.$refs.info.open(
() => this.$t('日志详情:{id}', { id: row.id }),
() =>
this.$API.sys_loginlog.get.post({
id: row.id, id: row.id,
}) }),
this.$refs.info.open(this.$TOOL.sortProperties(res.data), this.$t('日志详情:{id}', { id: row.id })) )
}, },
}, },
mounted() { mounted() {

View File

@ -134,7 +134,8 @@
</el-table-column> </el-table-column>
<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="{ row }"> <template #default="{ row }">
<na-ip :ip="row.createdClientIp"></na-ip> <p>{{ row.createdClientIp }}</p>
<p>{{ this.ips.filter((x) => x.ip === row.createdClientIp)[0]?.region ?? '...' }}</p>
</template> </template>
</el-table-column> </el-table-column>
<na-col-operation <na-col-operation
@ -161,7 +162,7 @@
<script> <script>
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import http from '@/utils/request'
const saveDialog = defineAsyncComponent(() => import('@/views/sys/user/save.vue')) const saveDialog = defineAsyncComponent(() => import('@/views/sys/user/save.vue'))
import naInfo from '@/components/naInfo/index.vue' import naInfo from '@/components/naInfo/index.vue'
@ -183,6 +184,7 @@ export default {
}, },
owners: [], owners: [],
apis: [], apis: [],
ips: [],
loading: false, loading: false,
query: { query: {
dynamicFilter: { dynamicFilter: {
@ -213,6 +215,7 @@ export default {
this.apis = [] this.apis = []
const ownerIds = data.data.rows?.filter((x) => x.ownerId).map((x) => x.ownerId) const ownerIds = data.data.rows?.filter((x) => x.ownerId).map((x) => x.ownerId)
const apiCrcs = data.data.rows?.map((x) => x.apiPathCrc32) const apiCrcs = data.data.rows?.map((x) => x.apiPathCrc32)
const ips = data.data.rows?.map((x) => x.createdClientIp)
const res = await Promise.all([ const res = await Promise.all([
ownerIds && ownerIds.length > 0 ownerIds && ownerIds.length > 0
? this.$API.sys_user.query.post({ ? this.$API.sys_user.query.post({
@ -233,9 +236,12 @@ export default {
}, },
}) })
: new Promise((x) => x({ data: [] })), : new Promise((x) => x({ data: [] })),
ips && ips.length > 0 ? http.get(`http://ip.line92.xyz/?ip=${ips.join('&ip=')}`) : new Promise((x) => x({ data: [] })),
]) ])
this.owners = res[0].data this.owners = res[0].data
this.apis = res[1].data this.apis = res[1].data
this.ips = res[2]
}, },
filterChange(data) { filterChange(data) {
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {
@ -297,11 +303,14 @@ export default {
async rowClick(row) { async rowClick(row) {
this.dialog.info = true this.dialog.info = true
await this.$nextTick() await this.$nextTick()
const res = await this.$API.sys_requestlog.get.post({ await this.$refs.info.open(
() => this.$t('日志详情:{id}', { id: row.id }),
() =>
this.$API.sys_requestlog.get.post({
id: row.id, id: row.id,
createdTime: row.createdTime, createdTime: row.createdTime,
}) }),
this.$refs.info.open(this.$TOOL.sortProperties(res.data), this.$t('日志详情:{id}', { id: row.id })) )
}, },
}, },
mounted() { mounted() {