fix 修复websocket在某些情况下不重连问题

fix 修复登录日志查看权限
feat 访问日志增加接口信息显示
perf 为所有orm的Insert操作增加OmitEmptyData选项
This commit is contained in:
孟帅
2024-04-24 23:25:29 +08:00
parent 269b2f9e43
commit dc20a86b33
57 changed files with 622 additions and 401 deletions

View File

@@ -97,7 +97,7 @@
"stylelint-config-standard": "^29.0.0",
"stylelint-order": "^5.0.0",
"stylelint-scss": "^4.7.0",
"tailwindcss": "^3.4.3",
"tailwindcss": "^2.2.19",
"typescript": "^5.3.0",
"unplugin-vue-components": "^0.22.12",
"vite": "^4.5.3",

View File

@@ -18,15 +18,6 @@ export function Delete(params) {
});
}
// 获取登录日志指定详情
export function View(params) {
return http.request({
url: '/loginLog/view',
method: 'GET',
params,
});
}
// 导出登录日志
export function Export(params) {
jumpExport('/loginLog/export', params);

View File

@@ -49,21 +49,29 @@ export default () => {
let timer: ReturnType<typeof setTimeout>;
const createSocket = () => {
console.log('[WebSocket] createSocket...');
if (useUserStore.token === '') {
if (useUserStore.token === '' || useUserStore.config?.wsAddr == '') {
console.error('[WebSocket] 用户未登录,稍后重试...');
reconnect();
resetReconnect();
return;
}
try {
socket = new WebSocket(`${useUserStore.config?.wsAddr}?authorization=${useUserStore.token}`);
init();
if (lockReconnect) {
lockReconnect = false;
}
} catch (e) {
console.error(`[WebSocket] createSocket err: ${e}`);
reconnect();
resetReconnect();
return;
}
};
const resetReconnect = () => {
if (lockReconnect) {
lockReconnect = false;
}
reconnect();
};
const reconnect = () => {
@@ -73,7 +81,7 @@ export default () => {
clearTimeout(timer);
timer = setTimeout(() => {
createSocket();
}, SocketEnum.HeartBeatInterval);
}, 2000);
};
const init = () => {

View File

@@ -64,9 +64,9 @@
{
field: 'url',
component: 'NInput',
label: '访问路径',
label: '接口路径',
componentProps: {
placeholder: '请输入访问路径',
placeholder: '请输入接口路径',
onInput: (e: any) => {
console.log(e);
},

View File

@@ -1,84 +1,147 @@
import { h } from 'vue';
import { NTag } from 'naive-ui';
import { NTag, NEllipsis, NSpace } from 'naive-ui';
import { timestampToTime } from '@/utils/dateUtil';
import { renderHtmlTooltip } from '@/utils';
export const columns = [
{
title: '模块',
key: 'module',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.module == 'admin' ? 'info' : 'success',
bordered: false,
},
{
default: () => row.module,
}
);
},
title: '记录ID',
key: 'id',
width: 100,
},
{
title: '操作人',
key: 'memberName',
render(row) {
if (row.memberId === 0) {
return row.memberName;
}
return row.memberName + '(' + row.memberId + ')';
},
width: 150,
},
{
title: '请求方式',
key: 'method',
width: 80,
},
{
title: '请求路径',
key: 'url',
width: 200,
},
{
title: '访问IP',
key: 'ip',
width: 150,
},
// {
// title: 'IP地区',
// key: 'region',
// },
{
title: '状态码',
key: 'errorCode',
title: '访客',
key: 'name',
width: 180,
render(row) {
const operator =
row.memberId === 0 ? row.memberName : row.memberName + '(' + row.memberId + ')';
return h(
NTag,
NEllipsis,
{
style: {
marginRight: '6px',
maxWidth: '180px',
},
type: row.errorCode == 0 ? 'success' : 'warning',
bordered: false,
},
{
default: () => row.errorMsg + '(' + row.errorCode + ')',
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
h('div', {
innerHTML: '<div><p>' + operator + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>IP' + row.ip + '</p></div>',
}),
row.cityLabel != ''
? h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'primary',
bordered: false,
},
{
default: () => row.cityLabel,
}
)
: null,
],
}
),
}
);
},
width: 150,
},
{
title: '处理耗时',
key: 'takeUpTime',
title: '请求接口',
key: 'name',
width: 260,
render(row) {
return row.takeUpTime + ' ms';
return h(
NEllipsis,
{
style: {
maxWidth: '260px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
h(
NTag,
{
style: {
marginRight: '6px',
},
bordered: false,
},
{
default: () => row.method,
}
),
h('div', {
innerHTML: '<div><p>接口:' + row.url + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>名称:' + row.tags + ' / ' + row.summary + '</p></div>',
}),
],
}
),
}
);
},
},
{
title: '接口响应',
key: 'name',
width: 260,
render(row) {
return h(
NEllipsis,
{
style: {
maxWidth: '260px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
renderHtmlTooltip(
'<div style="width: 240px"><p>状态码:' +
row.errorMsg +
'(' +
row.errorCode +
')' +
'</p></div>'
),
h('div', {
innerHTML: '<div><p>处理耗时:' + row.takeUpTime + 'ms</p></div>',
}),
h('div', {
innerHTML: '<div><p>响应时间:' + timestampToTime(row.timestamp) + '</p></div>',
}),
],
}
),
}
);
},
width: 120,
},
{
title: '访问时间',

View File

@@ -39,150 +39,26 @@
</template>
<script lang="ts" setup>
import { computed, h, reactive, ref } from 'vue';
import { computed, h, onMounted, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { BasicForm, useForm } from '@/components/Form/index';
import { getLogList, Delete } from '@/api/log/log';
import { columns } from './columns';
import { useRouter } from 'vue-router';
import { DeleteOutlined } from '@vicons/antd';
import { adaTableScrollX } from '@/utils/hotgo';
import { loadOptions, schemas } from './model';
const dialog = useDialog();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const schemas: FormSchema[] = [
{
field: 'member_id',
component: 'NInput',
label: '操作人',
componentProps: {
placeholder: '请输入操作人ID',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'url',
component: 'NInput',
label: '访问路径',
componentProps: {
placeholder: '请输入访问路径',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'ip',
component: 'NInput',
label: '访问IP',
componentProps: {
placeholder: '请输入IP地址',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'method',
component: 'NSelect',
label: '请求方式',
componentProps: {
placeholder: '请选择请求方式',
options: [
{
label: 'GET',
value: 'GET',
},
{
label: 'POST',
value: 'POST',
},
],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'created_at',
component: 'NDatePicker',
label: '访问时间',
componentProps: {
type: 'datetimerange',
clearable: true,
// defaultValue: [new Date() - 86400000 * 30, new Date()],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'take_up_time',
component: 'NSelect',
label: '请求耗时',
componentProps: {
placeholder: '请选择请求耗时',
options: [
{
label: '50ms内',
value: '50',
},
{
label: '100ms内',
value: '100',
},
{
label: '200ms内',
value: '200',
},
{
label: '500ms内',
value: '500',
},
],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'error_code',
component: 'NSelect',
label: '状态码',
componentProps: {
placeholder: '请选择状态码',
options: [
{
label: '0 成功',
value: '0',
},
{
label: '-1 失败',
value: '-1',
},
],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
];
const router = useRouter();
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
const params = ref({
pageSize: 10,
});
const actionColumn = reactive({
width: 160,
title: '操作',
@@ -233,9 +109,6 @@
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
@@ -251,14 +124,11 @@
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
const loadDataTable = async (res) => {
return await getLogList({ ...formParams.value, ...params.value, ...res });
return await getLogList({ ...formParams.value, ...res });
};
function reloadTable() {
@@ -278,6 +148,10 @@
formParams.value = {};
reloadTable();
}
onMounted(() => {
loadOptions();
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,134 @@
import { FormSchema } from '@/components/Form';
import { ref } from 'vue';
import { defRangeShortcuts } from '@/utils/dateUtil';
import { Option } from '@/utils/hotgo';
import { Dicts } from '@/api/dict/dict';
export const schemas = ref<FormSchema[]>([
{
field: 'reqId',
component: 'NInput',
label: '链路ID',
componentProps: {
placeholder: '请输入链路ID',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'memberId',
component: 'NInput',
label: '操作人',
componentProps: {
placeholder: '请输入操作人ID',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'url',
component: 'NInput',
label: '接口路径',
componentProps: {
placeholder: '请输入接口路径',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'ip',
component: 'NInput',
label: '访问IP',
componentProps: {
placeholder: '请输入IP地址',
onInput: (e: any) => {
console.log(e);
},
},
},
{
field: 'method',
component: 'NSelect',
label: '请求方式',
componentProps: {
placeholder: '请选择请求方式',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '访问时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'takeUpTime',
component: 'NSelect',
label: '请求耗时',
componentProps: {
placeholder: '请选择请求耗时',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'errorCode',
component: 'NSelect',
label: '状态码',
labelMessage: '支持填入自定义状态码',
componentProps: {
placeholder: '请选择状态码',
options: [],
filterable: true,
tag: true,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
// 字典数据选项
export const options = ref({
HTTPMethod: [] as Option[],
HTTPHandlerTime: [] as Option[],
HTTPApiCode: [] as Option[],
});
// 加载字典数据选项
export function loadOptions() {
Dicts({
types: ['HTTPMethod', 'HTTPHandlerTime', 'HTTPApiCode'],
}).then((res) => {
options.value = res;
for (const item of schemas.value) {
switch (item.field) {
case 'method':
item.componentProps.options = options.value.HTTPMethod;
break;
case 'takeUpTime':
item.componentProps.options = options.value.HTTPHandlerTime;
break;
case 'errorCode':
item.componentProps.options = options.value.HTTPApiCode;
break;
}
}
});
}

View File

@@ -14,15 +14,18 @@
<template #label>请求地址</template>
{{ data.url }}
</n-descriptions-item>
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
<n-descriptions-item label="接口名称"
>{{ data.tags }} / {{ data.summary }}</n-descriptions-item
>
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
<n-descriptions-item label="IP归属地">{{ data.cityLabel }}</n-descriptions-item>
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
<n-descriptions-item label="响应时间">{{
timestampToTime(data.timestamp)
data.timestamp > 0 ? timestampToTime(data.timestamp) : '--'
}}</n-descriptions-item>
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
<n-descriptions-item label="访问时间">{{ data.createdAt }}</n-descriptions-item>
</n-descriptions>
</n-card>
<n-card
@@ -131,22 +134,13 @@
const message = useMessage();
const router = useRouter();
const logId = Number(router.currentRoute.value.params.id);
const params = router.currentRoute.value.params;
const loading = ref(false);
onMounted(() => {
if (logId === undefined || logId < 1) {
message.error('ID不正确请检查');
return;
}
getInfo();
});
const data = ref({});
const getInfo = () => {
loading.value = true;
View({ id: logId })
View(params)
.then((res) => {
data.value = res;
})
@@ -154,6 +148,14 @@
loading.value = false;
});
};
onMounted(() => {
if (!params.id) {
message.error('日志ID不正确请检查');
return;
}
getInfo();
});
</script>
<style lang="less" scoped>

View File

@@ -77,7 +77,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
cssTarget: 'chrome80',
outDir: OUTPUT_DIR,
reportCompressedSize: false,
chunkSizeWarningLimit: 2000,
chunkSizeWarningLimit: 3000,
},
};
};