feat: 添加预算统计服务增强和日志系统改进
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 24s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 24s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
1. 新增 BudgetStatsService:将预算统计逻辑从 BudgetService 中提取为独立服务,支持月度和年度统计,包含归档数据支持和硬性预算调整算法 2. 日志系统增强:添加请求ID追踪功能,支持通过请求ID查询关联日志,新增类名筛选功能 3. 日志解析优化:修复类名解析逻辑,正确提取 SourceContext 中的类名信息 4. 代码清理:移除不需要的方法名相关代码,简化日志筛选逻辑
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import request from './request'
|
||||
import request from './request'
|
||||
|
||||
/**
|
||||
* 日志相关 API
|
||||
@@ -12,6 +12,7 @@
|
||||
* @param {string} [params.searchKeyword] - 搜索关键词
|
||||
* @param {string} [params.logLevel] - 日志级别
|
||||
* @param {string} [params.date] - 日期 (yyyyMMdd)
|
||||
* @param {string} [params.className] - 类名
|
||||
* @returns {Promise<{success: boolean, data: Array, total: number}>}
|
||||
*/
|
||||
export const getLogList = (params = {}) => {
|
||||
@@ -32,3 +33,34 @@ export const getAvailableDates = () => {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的类名列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {string} [params.date] - 日期 (yyyyMMdd)
|
||||
* @returns {Promise<{success: boolean, data: Array}>}
|
||||
*/
|
||||
export const getAvailableClassNames = (params = {}) => {
|
||||
return request({
|
||||
url: '/Log/GetAvailableClassNames',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据请求ID查询关联日志
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {string} params.requestId - 请求ID
|
||||
* @param {number} [params.pageIndex=1] - 页码
|
||||
* @param {number} [params.pageSize=50] - 每页条数
|
||||
* @param {string} [params.date] - 日期 (yyyyMMdd)
|
||||
* @returns {Promise<{success: boolean, data: Array, total: number}>}
|
||||
*/
|
||||
export const getLogsByRequestId = (params = {}) => {
|
||||
return request({
|
||||
url: '/Log/GetLogsByRequestId',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'axios'
|
||||
import axios from 'axios'
|
||||
import { showToast } from 'vant'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import router from '@/router'
|
||||
@@ -12,6 +12,15 @@ const request = axios.create({
|
||||
}
|
||||
})
|
||||
|
||||
// 生成请求ID
|
||||
const generateRequestId = () => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
@@ -20,6 +29,11 @@ request.interceptors.request.use(
|
||||
if (authStore.token) {
|
||||
config.headers.Authorization = `Bearer ${authStore.token}`
|
||||
}
|
||||
|
||||
// 添加请求ID
|
||||
const requestId = generateRequestId()
|
||||
config.headers['X-Request-ID'] = requestId
|
||||
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="page-container-flex log-view">
|
||||
<van-nav-bar
|
||||
title="查看日志"
|
||||
@@ -28,7 +28,12 @@
|
||||
<van-dropdown-item
|
||||
v-model="selectedDate"
|
||||
:options="dateOptions"
|
||||
@change="handleSearch"
|
||||
@change="handleDateChange"
|
||||
/>
|
||||
<van-dropdown-item
|
||||
v-model="selectedClassName"
|
||||
:options="classNameOptions"
|
||||
@change="handleClassNameChange"
|
||||
/>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
@@ -66,6 +71,31 @@
|
||||
<span class="log-level">{{ log.level }}</span>
|
||||
<span class="log-time">{{ formatTime(log.timestamp) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="log.className || log.methodName"
|
||||
class="log-source"
|
||||
>
|
||||
<span
|
||||
v-if="log.className"
|
||||
class="source-class"
|
||||
>{{ log.className }}</span>
|
||||
<span
|
||||
v-if="log.methodName"
|
||||
class="source-method"
|
||||
>.{{ log.methodName }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="log.requestId"
|
||||
class="log-request-id"
|
||||
>
|
||||
<span class="request-id-label">请求ID:</span>
|
||||
<span
|
||||
class="request-id-value"
|
||||
@click="handleRequestIdClick(log.requestId)"
|
||||
>
|
||||
{{ log.requestId }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="log-message">
|
||||
{{ log.message }}
|
||||
</div>
|
||||
@@ -90,7 +120,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
import { getLogList, getAvailableDates } from '@/api/log'
|
||||
import { getLogList, getAvailableDates, getAvailableClassNames, getLogsByRequestId } from '@/api/log'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -110,6 +140,11 @@ const total = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
const selectedLevel = ref('')
|
||||
const selectedDate = ref('')
|
||||
const selectedClassName = ref('')
|
||||
|
||||
// requestId 查询模式
|
||||
const isRequestIdMode = ref(false)
|
||||
const currentRequestId = ref('')
|
||||
|
||||
// 日志级别选项
|
||||
const levelOptions = ref([
|
||||
@@ -125,6 +160,9 @@ const levelOptions = ref([
|
||||
// 日期选项
|
||||
const dateOptions = ref([{ text: '全部日期', value: '' }])
|
||||
|
||||
// 类名选项
|
||||
const classNameOptions = ref([{ text: '全部类名', value: '' }])
|
||||
|
||||
/**
|
||||
* 返回上一页
|
||||
*/
|
||||
@@ -177,22 +215,37 @@ const loadLogs = async (reset = false) => {
|
||||
finished.value = false
|
||||
}
|
||||
|
||||
const params = {
|
||||
pageIndex: pageIndex.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
let response
|
||||
|
||||
if (searchKeyword.value) {
|
||||
params.searchKeyword = searchKeyword.value
|
||||
}
|
||||
if (selectedLevel.value) {
|
||||
params.logLevel = selectedLevel.value
|
||||
}
|
||||
if (selectedDate.value) {
|
||||
params.date = selectedDate.value
|
||||
}
|
||||
if (isRequestIdMode.value) {
|
||||
// requestId 查询模式
|
||||
response = await getLogsByRequestId({
|
||||
requestId: currentRequestId.value,
|
||||
pageIndex: pageIndex.value,
|
||||
pageSize: pageSize.value
|
||||
})
|
||||
} else {
|
||||
// 普通查询模式
|
||||
const params = {
|
||||
pageIndex: pageIndex.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
|
||||
const response = await getLogList(params)
|
||||
if (searchKeyword.value) {
|
||||
params.searchKeyword = searchKeyword.value
|
||||
}
|
||||
if (selectedLevel.value) {
|
||||
params.logLevel = selectedLevel.value
|
||||
}
|
||||
if (selectedDate.value) {
|
||||
params.date = selectedDate.value
|
||||
}
|
||||
if (selectedClassName.value) {
|
||||
params.className = selectedClassName.value
|
||||
}
|
||||
|
||||
response = await getLogList(params)
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
const newLogs = response.data || []
|
||||
@@ -206,12 +259,9 @@ const loadLogs = async (reset = false) => {
|
||||
total.value = response.total
|
||||
|
||||
// 判断是否还有更多数据
|
||||
// total = -1 表示总数未知,此时只根据返回数据量判断
|
||||
if (total.value === -1) {
|
||||
// 如果返回的数据少于请求的数量,说明没有更多了
|
||||
finished.value = newLogs.length < pageSize.value
|
||||
} else {
|
||||
// 如果有明确的总数,则判断是否已加载完全部数据
|
||||
if (logList.value.length >= total.value || newLogs.length < pageSize.value) {
|
||||
finished.value = true
|
||||
} else {
|
||||
@@ -237,7 +287,12 @@ const loadLogs = async (reset = false) => {
|
||||
* 下拉刷新
|
||||
*/
|
||||
const onRefresh = async () => {
|
||||
await loadLogs(true)
|
||||
if (isRequestIdMode.value) {
|
||||
// requestId 模式下刷新,重置为第一页
|
||||
await loadLogs(true)
|
||||
} else {
|
||||
await loadLogs(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,6 +317,8 @@ const onLoad = async () => {
|
||||
* 搜索处理
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
isRequestIdMode.value = false
|
||||
currentRequestId.value = ''
|
||||
loadLogs(true)
|
||||
}
|
||||
|
||||
@@ -291,6 +348,44 @@ const loadAvailableDates = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载可用类名列表
|
||||
*/
|
||||
const loadAvailableClassNames = async () => {
|
||||
try {
|
||||
const params = {}
|
||||
if (selectedDate.value) {
|
||||
params.date = selectedDate.value
|
||||
}
|
||||
const response = await getAvailableClassNames(params)
|
||||
if (response.success && response.data) {
|
||||
const classNames = response.data.map((name) => ({
|
||||
text: name,
|
||||
value: name
|
||||
}))
|
||||
classNameOptions.value = [{ text: '全部类名', value: '' }, ...classNames]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载类名列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期改变时重新加载类名
|
||||
*/
|
||||
const handleDateChange = async () => {
|
||||
selectedClassName.value = ''
|
||||
await loadAvailableClassNames()
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
/**
|
||||
* 类名改变时重新搜索
|
||||
*/
|
||||
const handleClassNameChange = () => {
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期显示
|
||||
*/
|
||||
@@ -302,9 +397,50 @@ const formatDate = (dateStr) => {
|
||||
return dateStr
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求ID点击
|
||||
*/
|
||||
const handleRequestIdClick = async (requestId) => {
|
||||
try {
|
||||
showToast('正在查询关联日志...')
|
||||
|
||||
isRequestIdMode.value = true
|
||||
currentRequestId.value = requestId
|
||||
|
||||
const response = await getLogsByRequestId({
|
||||
requestId,
|
||||
pageIndex: 1,
|
||||
pageSize: 100
|
||||
})
|
||||
|
||||
if (response.success && response.data && response.data.length > 0) {
|
||||
logList.value = response.data
|
||||
total.value = response.total
|
||||
pageIndex.value = 1
|
||||
|
||||
// 根据返回数据量判断是否还有更多
|
||||
if (response.data.length < 100) {
|
||||
finished.value = true
|
||||
} else {
|
||||
finished.value = false
|
||||
}
|
||||
|
||||
showToast(`找到 ${response.total} 条关联日志`)
|
||||
} else {
|
||||
showToast('未找到关联日志')
|
||||
logList.value = []
|
||||
finished.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询关联日志失败:', error)
|
||||
showToast('查询失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadAvailableDates()
|
||||
loadAvailableClassNames()
|
||||
// 不在这里调用 loadLogs,让 van-list 的 @load 事件自动触发
|
||||
})
|
||||
</script>
|
||||
@@ -399,6 +535,43 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.log-request-id {
|
||||
margin: 2px 0;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.log-source {
|
||||
margin: 2px 0;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.source-class {
|
||||
font-weight: bold;
|
||||
color: var(--van-primary-color);
|
||||
}
|
||||
|
||||
.request-id-label {
|
||||
margin-right: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.request-id-value {
|
||||
cursor: pointer;
|
||||
color: var(--van-primary-color);
|
||||
text-decoration: underline;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-id-value:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
color: #323233;
|
||||
line-height: 1.4;
|
||||
|
||||
Reference in New Issue
Block a user