新增业务外部接口日志管理

This commit is contained in:
wkc
2026-07-20 15:35:54 +08:00
parent 0a0313af7c
commit 2d9828ea5f
39 changed files with 1603 additions and 497 deletions

View File

@@ -0,0 +1,18 @@
import request from '@/utils/request'
// 查询业务外部接口日志列表
export function listApiLog(query) {
return request({
url: '/monitor/apilog/list',
method: 'get',
params: query
})
}
// 查询业务外部接口日志详情
export function getApiLog(logId) {
return request({
url: '/monitor/apilog/' + logId,
method: 'get'
})
}

View File

@@ -0,0 +1,289 @@
<template>
<div class="app-container">
<el-form
ref="queryForm"
:model="queryParams"
size="small"
:inline="true"
v-show="showSearch"
label-width="82px"
>
<el-form-item label="接口地址" prop="apiUrl">
<el-input
v-model="queryParams.apiUrl"
placeholder="请输入接口地址"
clearable
style="width: 260px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="HTTP方法" prop="httpMethod">
<el-select v-model="queryParams.httpMethod" placeholder="全部" clearable style="width: 140px">
<el-option label="GET" value="GET" />
<el-option label="POST" value="POST" />
</el-select>
</el-form-item>
<el-form-item label="HTTP调用状态" prop="callStatus">
<el-select v-model="queryParams.callStatus" placeholder="全部" clearable style="width: 140px">
<el-option label="成功" value="0" />
<el-option label="失败" value="1" />
</el-select>
</el-form-item>
<el-form-item label="调用时间">
<el-date-picker
v-model="dateRange"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="['00:00:00', '23:59:59']"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="logId" width="100" />
<el-table-column label="调用账号" align="center" prop="callerUsername" width="130" show-overflow-tooltip />
<el-table-column label="接口地址" prop="apiUrl" min-width="320" show-overflow-tooltip />
<el-table-column label="方法" align="center" prop="httpMethod" width="90">
<template slot-scope="scope">
<el-tag size="mini" effect="plain">{{ scope.row.httpMethod }}</el-tag>
</template>
</el-table-column>
<el-table-column label="HTTP状态" align="center" prop="responseStatus" width="100">
<template slot-scope="scope">
<span>{{ scope.row.responseStatus == null ? '-' : scope.row.responseStatus }}</span>
</template>
</el-table-column>
<el-table-column label="HTTP调用状态" align="center" prop="callStatus" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.callStatus === '0'" type="success" size="mini">成功</el-tag>
<el-tag v-else type="danger" size="mini">失败</el-tag>
</template>
</el-table-column>
<el-table-column label="耗时" align="right" prop="costTime" width="110">
<template slot-scope="scope">{{ scope.row.costTime }} 毫秒</template>
</el-table-column>
<el-table-column label="调用时间" align="center" prop="callTime" width="165">
<template slot-scope="scope">{{ parseTime(scope.row.callTime) }}</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="90">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
v-hasPermi="['monitor:apilog:query']"
@click="handleView(scope.row)"
>详细</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog
title="接口日志详细"
:visible.sync="open"
width="90%"
custom-class="api-log-dialog"
append-to-body
>
<div v-loading="detailLoading" class="api-log-detail">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="调用账号">{{ detail.callerUsername || '-' }}</el-descriptions-item>
<el-descriptions-item label="用户ID">{{ detail.callerUserId == null ? '-' : detail.callerUserId }}</el-descriptions-item>
<el-descriptions-item label="HTTP方法">{{ detail.httpMethod || '-' }}</el-descriptions-item>
<el-descriptions-item label="Content-Type">{{ detail.contentType || '-' }}</el-descriptions-item>
<el-descriptions-item label="HTTP状态">{{ detail.responseStatus == null ? '-' : detail.responseStatus }}</el-descriptions-item>
<el-descriptions-item label="HTTP调用状态">{{ detail.callStatus === '0' ? '成功' : '失败' }}</el-descriptions-item>
<el-descriptions-item label="耗时">{{ detail.costTime == null ? '-' : detail.costTime + ' 毫秒' }}</el-descriptions-item>
<el-descriptions-item label="调用时间">{{ parseTime(detail.callTime) }}</el-descriptions-item>
<el-descriptions-item label="接口地址" :span="2">{{ detail.apiUrl || '-' }}</el-descriptions-item>
</el-descriptions>
<section class="detail-section">
<h4>请求头</h4>
<pre class="log-content">{{ formatLogContent(detail.requestHeaders) }}</pre>
</section>
<section class="detail-section">
<h4>请求参数</h4>
<pre class="log-content">{{ formatLogContent(detail.requestParams) }}</pre>
</section>
<section class="detail-section">
<h4>响应头</h4>
<pre class="log-content">{{ formatLogContent(detail.responseHeaders) }}</pre>
</section>
<section class="detail-section">
<h4>原始返回正文</h4>
<pre class="log-content log-content-large">{{ formatLogContent(detail.responseBody) }}</pre>
</section>
<section v-if="detail.errorMsg" class="detail-section">
<h4 class="error-title">异常信息</h4>
<pre class="log-content error-content">{{ formatLogContent(detail.errorMsg) }}</pre>
</section>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { js_beautify } from 'js-beautify'
import { getApiLog, listApiLog } from '@/api/monitor/apilog'
export default {
name: 'ApiLog',
data() {
return {
loading: true,
detailLoading: false,
showSearch: true,
total: 0,
list: [],
open: false,
detail: {},
dateRange: [],
queryParams: {
pageNum: 1,
pageSize: 10,
apiUrl: undefined,
httpMethod: undefined,
callStatus: undefined
}
}
},
created() {
this.getList()
},
methods: {
getList() {
this.loading = true
const query = { ...this.queryParams }
if (this.dateRange && this.dateRange.length === 2) {
query.beginTime = this.dateRange[0]
query.endTime = this.dateRange[1]
}
listApiLog(query).then(response => {
this.list = response.rows
this.total = response.total
}).finally(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.dateRange = []
this.resetForm('queryForm')
this.queryParams.pageNum = 1
this.getList()
},
formatLogContent(value) {
if (value === null || value === undefined || value === '') {
return '-'
}
if (typeof value !== 'string') {
return JSON.stringify(value, null, 2)
}
try {
JSON.parse(value)
return js_beautify(value, { indent_size: 2 })
} catch (error) {
return value
}
},
handleView(row) {
this.open = true
this.detail = {}
this.detailLoading = true
getApiLog(row.logId).then(response => {
this.detail = response.data || {}
}).finally(() => {
this.detailLoading = false
})
}
}
}
</script>
<style scoped>
.api-log-detail {
min-height: 180px;
}
::v-deep .api-log-dialog {
max-width: 920px;
}
::v-deep .api-log-dialog .el-descriptions-item__content {
overflow-wrap: anywhere;
}
.detail-section {
margin-top: 16px;
}
.detail-section h4 {
margin: 0 0 8px;
color: #303133;
font-size: 14px;
font-weight: 600;
line-height: 20px;
letter-spacing: 0;
}
.log-content {
box-sizing: border-box;
width: 100%;
max-height: 220px;
margin: 0;
overflow: auto;
padding: 12px;
border: 1px solid #dcdfe6;
border-radius: 6px;
background: #f7f8fa;
color: #303133;
font-family: Monaco, Menlo, Consolas, monospace;
font-size: 12px;
line-height: 1.55;
letter-spacing: 0;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
.log-content-large {
max-height: 320px;
}
.error-title {
color: #c03639 !important;
}
.error-content {
border-color: #f3c5c7;
background: #fff6f6;
color: #9f2d30;
}
</style>