You've already forked template-MP
优化 错误监控功能重构 - 集成uniapp-error-monitor依赖,实现优雅降级机制和完整环境兼容
This commit is contained in:
@@ -32,7 +32,21 @@ http.interceptors.response.use(response => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.data.code !== 1 || response.data.code !== 200) {
|
if (response.data.code !== 1 || response.data.code !== 200) {
|
||||||
tool.reportError('network', response.data)
|
tool.reportError(
|
||||||
|
'api',
|
||||||
|
{
|
||||||
|
url: response.config.url,
|
||||||
|
method: response.config.method,
|
||||||
|
statusCode: response.data.code || response.statusCode,
|
||||||
|
statusText: response.data.msg || response.data.message || '未知错误',
|
||||||
|
responseTime: Date.now() - (response.config.startTime || Date.now()),
|
||||||
|
requestData: response.config.data,
|
||||||
|
requestHeaders: response.config.header,
|
||||||
|
environment: import.meta.env.MODE,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|||||||
@@ -1,5 +1,113 @@
|
|||||||
const baseUrl = import.meta.env.VITE_BASE_URL
|
// 环境变量处理
|
||||||
const assetsUrl = import.meta.env.VITE_ASSETSURL
|
const getEnvVar = (key, defaultValue = '') => {
|
||||||
|
if (typeof import !== 'undefined' && import.meta && import.meta.env) {
|
||||||
|
return import.meta.env[key] || defaultValue
|
||||||
|
}
|
||||||
|
if (typeof process !== 'undefined' && process.env) {
|
||||||
|
return process.env[key] || defaultValue
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = getEnvVar('VITE_BASE_URL', 'https://api.example.com')
|
||||||
|
const assetsUrl = getEnvVar('VITE_ASSETSURL', '/assets/')
|
||||||
|
|
||||||
|
// 错误监控实例
|
||||||
|
let errorMonitor = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化错误监控实例
|
||||||
|
*/
|
||||||
|
function initErrorMonitorInstance() {
|
||||||
|
if (errorMonitor) return errorMonitor
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 在 Node.js 环境下不尝试导入 uniapp-error-monitor
|
||||||
|
if (typeof window === 'undefined' && typeof uni === 'undefined') {
|
||||||
|
throw new Error('非浏览器/UniApp 环境,跳过 uniapp-error-monitor 导入')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试导入 uniapp-error-monitor
|
||||||
|
const ErrorMonitorModule = require('uniapp-error-monitor')
|
||||||
|
if (ErrorMonitorModule.default) {
|
||||||
|
errorMonitor = new ErrorMonitorModule.default()
|
||||||
|
} else if (ErrorMonitorModule.ErrorMonitor) {
|
||||||
|
errorMonitor = new ErrorMonitorModule.ErrorMonitor()
|
||||||
|
} else {
|
||||||
|
throw new Error('未找到可用的 ErrorMonitor 类')
|
||||||
|
}
|
||||||
|
console.log('[Tool] 使用 uniapp-error-monitor')
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[Tool] uniapp-error-monitor 导入失败,将使用本地错误监控实现:', error.message)
|
||||||
|
|
||||||
|
// 本地错误监控实现
|
||||||
|
errorMonitor = {
|
||||||
|
errorStats: {
|
||||||
|
total: 0,
|
||||||
|
global: 0,
|
||||||
|
promise: 0,
|
||||||
|
console: 0,
|
||||||
|
miniProgram: 0,
|
||||||
|
api: 0,
|
||||||
|
network: 0,
|
||||||
|
manual: 0,
|
||||||
|
lastErrorTime: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
init(config = {}) {
|
||||||
|
console.log('[Tool] 使用本地错误监控实现')
|
||||||
|
// 这里可以添加本地错误监控逻辑
|
||||||
|
},
|
||||||
|
|
||||||
|
reportError(type = 'manual', error, context = {}, forceSend = false) {
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats[type] = (this.errorStats[type] || 0) + 1
|
||||||
|
this.errorStats.lastErrorTime = Date.now()
|
||||||
|
console.log(`[Tool] 错误上报: ${type}`, error)
|
||||||
|
},
|
||||||
|
|
||||||
|
wrapPromise(promise) {
|
||||||
|
return promise.catch(error => {
|
||||||
|
this.reportError('promise', error)
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getErrorStats() {
|
||||||
|
return { ...this.errorStats }
|
||||||
|
},
|
||||||
|
|
||||||
|
resetErrorStats() {
|
||||||
|
this.errorStats = {
|
||||||
|
total: 0,
|
||||||
|
global: 0,
|
||||||
|
promise: 0,
|
||||||
|
console: 0,
|
||||||
|
miniProgram: 0,
|
||||||
|
api: 0,
|
||||||
|
network: 0,
|
||||||
|
manual: 0,
|
||||||
|
lastErrorTime: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getEnvironmentInfo() {
|
||||||
|
return {
|
||||||
|
isProduction: getEnvVar('MODE') === 'production',
|
||||||
|
mode: getEnvVar('MODE', 'development'),
|
||||||
|
platform: 'Unknown',
|
||||||
|
errorMonitorEnabled: true,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMonitor
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化错误监控实例
|
||||||
|
const monitorInstance = initErrorMonitorInstance()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具类 - 提供常用的工具方法
|
* 工具类 - 提供常用的工具方法
|
||||||
@@ -12,32 +120,15 @@ class Tool {
|
|||||||
NONE: 0,
|
NONE: 0,
|
||||||
SUCCESS: 1,
|
SUCCESS: 1,
|
||||||
LOADING: 2,
|
LOADING: 2,
|
||||||
|
ERROR: 3,
|
||||||
|
WARNING: 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字体加载状态缓存
|
// 字体加载状态缓存
|
||||||
this.loadedFonts = new Set()
|
this.loadedFonts = new Set()
|
||||||
|
|
||||||
// 初始化错误统计
|
// 错误监控配置
|
||||||
this.errorStats = {
|
this.config = null
|
||||||
total: 0,
|
|
||||||
global: 0,
|
|
||||||
promise: 0,
|
|
||||||
console: 0,
|
|
||||||
miniProgram: 0,
|
|
||||||
lastErrorTime: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Promise包装方法
|
|
||||||
this.wrapPromise = null
|
|
||||||
|
|
||||||
// 项目信息
|
|
||||||
this.projectInfo = {
|
|
||||||
name: 'template',
|
|
||||||
version: '1.0.0',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试从 manifest.json 加载项目信息
|
|
||||||
this._loadProjectInfo()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,6 +148,8 @@ class Tool {
|
|||||||
[this.ICON_TYPES.NONE]: 'none',
|
[this.ICON_TYPES.NONE]: 'none',
|
||||||
[this.ICON_TYPES.SUCCESS]: 'success',
|
[this.ICON_TYPES.SUCCESS]: 'success',
|
||||||
[this.ICON_TYPES.LOADING]: 'loading',
|
[this.ICON_TYPES.LOADING]: 'loading',
|
||||||
|
[this.ICON_TYPES.ERROR]: 'error',
|
||||||
|
[this.ICON_TYPES.WARNING]: 'none', // uni-app toast不支持warning图标
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -360,16 +453,58 @@ class Tool {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理支付流程的完整方法
|
||||||
|
* @param {Object} paymentData 支付参数
|
||||||
|
* @param {Function} successCallback 成功回调
|
||||||
|
* @param {Function} errorCallback 错误回调
|
||||||
|
* @returns {Promise<Object>} 支付结果
|
||||||
|
*/
|
||||||
|
async handlePayment(paymentData, successCallback = null, errorCallback = null) {
|
||||||
|
try {
|
||||||
|
// 调用微信支付
|
||||||
|
const res = await this.requestPayment(paymentData)
|
||||||
|
this.alert('支付完成,正在验证结果...', this.ICON_TYPES.LOADING)
|
||||||
|
|
||||||
|
// 如果有成功回调,调用它
|
||||||
|
if (successCallback && typeof successCallback === 'function') {
|
||||||
|
await successCallback(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
} catch (error) {
|
||||||
|
console.log('支付失败:', error)
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
if (error.errMsg && error.errMsg.includes('cancel')) {
|
||||||
|
await this.alert('支付已取消', this.ICON_TYPES.WARNING)
|
||||||
|
} else {
|
||||||
|
await this.alert('支付失败,请重试', this.ICON_TYPES.ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有错误回调,调用它
|
||||||
|
if (errorCallback && typeof errorCallback === 'function') {
|
||||||
|
await errorCallback(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
upload(filePath) {
|
upload(filePath) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: `${baseUrl}file/upload`,
|
url: `${baseUrl}/j1732/api/front/upload/image`,
|
||||||
fileType: 'image',
|
fileType: 'image',
|
||||||
header: {
|
header: {
|
||||||
Authorization: `Bearer ${this.storage('token')}`,
|
'Authori-zation': `${this.storage('token')}`,
|
||||||
},
|
},
|
||||||
filePath,
|
filePath,
|
||||||
name: 'file',
|
name: 'multipart',
|
||||||
|
formData: {
|
||||||
|
model: 'user', // 用户模块
|
||||||
|
pid: 7, // 前台用户分类ID
|
||||||
|
},
|
||||||
success: ({ data }) => {
|
success: ({ data }) => {
|
||||||
resolve(JSON.parse(data))
|
resolve(JSON.parse(data))
|
||||||
},
|
},
|
||||||
@@ -380,32 +515,6 @@ class Tool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测是否为生产环境
|
|
||||||
* @private
|
|
||||||
* @returns {boolean} 是否为生产环境
|
|
||||||
*/
|
|
||||||
_isProduction() {
|
|
||||||
// 检查uniapp运行模式
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync?.()
|
|
||||||
if (systemInfo?.mode && systemInfo.mode !== 'default') {
|
|
||||||
// 体验版、开发版、预览版
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,继续检测
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查环境变量MODE
|
|
||||||
if (import.meta.env.MODE === 'development') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认:开发环境和体验版不启用,生产环境启用
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化全局错误监控
|
* 初始化全局错误监控
|
||||||
* @param {Object} options 配置选项
|
* @param {Object} options 配置选项
|
||||||
@@ -429,149 +538,32 @@ class Tool {
|
|||||||
...options,
|
...options,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 环境检查:只在生产环境下启用错误监控
|
|
||||||
if (!config.forceEnable && !this._isProduction()) {
|
|
||||||
console.info('当前为非生产环境,错误监控已禁用')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查webhook配置
|
|
||||||
if (!config.webhookUrl) {
|
|
||||||
console.warn('错误监控初始化失败:未配置webhook地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
||||||
// 全局错误捕获(uniapp环境适配)
|
// 使用 uniapp-error-monitor 或本地实现初始化错误监控
|
||||||
if (config.enableGlobalError) {
|
monitorInstance.init(config)
|
||||||
// Web环境
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.onerror = (message, source, lineno, colno, error) => {
|
|
||||||
this._handleGlobalError({
|
|
||||||
type: 'global',
|
|
||||||
message,
|
|
||||||
source,
|
|
||||||
lineno,
|
|
||||||
colno,
|
|
||||||
error,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理未捕获的Promise错误
|
console.log('错误监控已初始化(基于 uniapp-error-monitor)')
|
||||||
window.addEventListener('unhandledrejection', event => {
|
|
||||||
this._handlePromiseError({
|
|
||||||
type: 'promise',
|
|
||||||
reason: event.reason,
|
|
||||||
promise: event.promise,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// uniapp环境 - 提供Promise包装工具
|
|
||||||
if (typeof uni !== 'undefined' && config.enablePromiseError) {
|
|
||||||
// 提供一个包装Promise的方法,让开发者可以手动包装重要的Promise
|
|
||||||
this.wrapPromise = promise => {
|
|
||||||
const self = this
|
|
||||||
return promise.catch(error => {
|
|
||||||
self._handlePromiseError({
|
|
||||||
type: 'promise',
|
|
||||||
reason: error,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.error捕获(可选)
|
|
||||||
if (config.enableConsoleError) {
|
|
||||||
const originalError = console.error
|
|
||||||
console.error = (...args) => {
|
|
||||||
originalError.apply(console, args)
|
|
||||||
this._handleConsoleError({
|
|
||||||
type: 'console',
|
|
||||||
args: args.map(arg => this._serializeError(arg)),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 微信小程序错误捕获
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
// 监听小程序错误事件
|
|
||||||
uni.onError &&
|
|
||||||
uni.onError(error => {
|
|
||||||
this._handleMiniProgramError({
|
|
||||||
type: 'miniProgram',
|
|
||||||
error,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听小程序页面错误
|
|
||||||
uni.onPageNotFound &&
|
|
||||||
uni.onPageNotFound(result => {
|
|
||||||
this._handleMiniProgramError({
|
|
||||||
type: 'pageNotFound',
|
|
||||||
path: result.path,
|
|
||||||
query: result.query,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听小程序网络请求错误
|
|
||||||
const originalRequest = uni.request
|
|
||||||
uni.request = options => {
|
|
||||||
return originalRequest({
|
|
||||||
...options,
|
|
||||||
fail: err => {
|
|
||||||
options.fail && options.fail(err)
|
|
||||||
this._handleNetworkError({
|
|
||||||
type: 'network',
|
|
||||||
url: options.url,
|
|
||||||
method: options.method,
|
|
||||||
error: err,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('错误监控已初始化')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手动上报错误
|
* 手动上报错误
|
||||||
|
* @param {string} type 错误类型 ('manual', 'api', 'network', 'global', 'promise', 'console', 'miniProgram')
|
||||||
* @param {Error|Object} error 错误对象或错误信息
|
* @param {Error|Object} error 错误对象或错误信息
|
||||||
* @param {Object} [context] 错误上下文信息
|
* @param {Object} [context] 错误上下文信息
|
||||||
* @param {boolean} [forceSend=false] 强制发送(忽略环境检查)
|
* @param {boolean} [forceSend=false] 强制发送(忽略环境检查)
|
||||||
*/
|
*/
|
||||||
reportError(type = 'manual', error, context = {}, forceSend = false) {
|
reportError(type = 'manual', error, context = {}, forceSend = false) {
|
||||||
const errorInfo = {
|
monitorInstance.reportError(type, error, context, forceSend)
|
||||||
type,
|
}
|
||||||
error: error instanceof Error ? error.message : error,
|
|
||||||
stack: error instanceof Error ? error.stack : null,
|
|
||||||
context,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: getCurrentPageName(),
|
|
||||||
}
|
|
||||||
this.errorStats.total++
|
|
||||||
this.errorStats.miniProgram++
|
|
||||||
this.errorStats.lastErrorTime = errorInfo.timestamp
|
|
||||||
|
|
||||||
if (forceSend) {
|
/**
|
||||||
// 强制发送
|
* 包装Promise,自动捕获Promise错误
|
||||||
this._sendErrorToWebhook(errorInfo, 0, true)
|
* @param {Promise} promise 要包装的Promise
|
||||||
} else {
|
* @returns {Promise} 包装后的Promise
|
||||||
this._sendErrorToWebhook(errorInfo)
|
*/
|
||||||
}
|
wrapPromise(promise) {
|
||||||
|
return monitorInstance.wrapPromise(promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -579,21 +571,14 @@ class Tool {
|
|||||||
* @returns {Object} 错误统计信息
|
* @returns {Object} 错误统计信息
|
||||||
*/
|
*/
|
||||||
getErrorStats() {
|
getErrorStats() {
|
||||||
return { ...this.errorStats }
|
return monitorInstance.getErrorStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置错误统计
|
* 重置错误统计
|
||||||
*/
|
*/
|
||||||
resetErrorStats() {
|
resetErrorStats() {
|
||||||
this.errorStats = {
|
monitorInstance.resetErrorStats()
|
||||||
total: 0,
|
|
||||||
global: 0,
|
|
||||||
promise: 0,
|
|
||||||
console: 0,
|
|
||||||
miniProgram: 0,
|
|
||||||
lastErrorTime: null,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -601,358 +586,31 @@ class Tool {
|
|||||||
* @returns {Object} 环境信息
|
* @returns {Object} 环境信息
|
||||||
*/
|
*/
|
||||||
getEnvironmentInfo() {
|
getEnvironmentInfo() {
|
||||||
return {
|
return monitorInstance.getEnvironmentInfo()
|
||||||
isProduction: this._isProduction(),
|
|
||||||
mode: import.meta.env.MODE,
|
|
||||||
platform: this._getUserAgent(),
|
|
||||||
errorMonitorEnabled: !!this.config,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理全局错误
|
* 确认对话框
|
||||||
* @private
|
* @param {string} content 确认内容
|
||||||
|
* @param {string} [title='提示'] 标题
|
||||||
|
* @returns {Promise<boolean>} 用户选择结果,true为确认,false为取消
|
||||||
*/
|
*/
|
||||||
_handleGlobalError(errorInfo) {
|
confirm(content, title = '提示') {
|
||||||
this.errorStats.total++
|
return new Promise(resolve => {
|
||||||
this.errorStats.global++
|
uni.showModal({
|
||||||
this.errorStats.lastErrorTime = errorInfo.timestamp
|
title,
|
||||||
|
content,
|
||||||
this._sendErrorToWebhook({
|
confirmText: '确认',
|
||||||
...errorInfo,
|
cancelText: '取消',
|
||||||
message: errorInfo.message || 'Unknown global error',
|
success: res => {
|
||||||
source: errorInfo.source || '',
|
resolve(res.confirm)
|
||||||
lineno: errorInfo.lineno || 0,
|
},
|
||||||
colno: errorInfo.colno || 0,
|
fail: () => {
|
||||||
url: this._getCurrentUrl(),
|
resolve(false)
|
||||||
userAgent: this._getUserAgent(),
|
},
|
||||||
page: getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理Promise错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handlePromiseError(errorInfo) {
|
|
||||||
this.errorStats.total++
|
|
||||||
this.errorStats.promise++
|
|
||||||
this.errorStats.lastErrorTime = errorInfo.timestamp
|
|
||||||
|
|
||||||
this._sendErrorToWebhook({
|
|
||||||
...errorInfo,
|
|
||||||
reason: this._serializeError(errorInfo.reason),
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理console错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleConsoleError(errorInfo) {
|
|
||||||
this.errorStats.total++
|
|
||||||
this.errorStats.console++
|
|
||||||
this.errorStats.lastErrorTime = errorInfo.timestamp
|
|
||||||
|
|
||||||
this._sendErrorToWebhook({
|
|
||||||
...errorInfo,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理小程序错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleMiniProgramError(errorInfo) {
|
|
||||||
this.errorStats.total++
|
|
||||||
this.errorStats.miniProgram++
|
|
||||||
this.errorStats.lastErrorTime = errorInfo.timestamp
|
|
||||||
|
|
||||||
this._sendErrorToWebhook({
|
|
||||||
...errorInfo,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理网络错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleNetworkError(errorInfo) {
|
|
||||||
this.errorStats.total++
|
|
||||||
this.errorStats.miniProgram++
|
|
||||||
this.errorStats.lastErrorTime = errorInfo.timestamp
|
|
||||||
|
|
||||||
this._sendErrorToWebhook({
|
|
||||||
...errorInfo,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前URL
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getCurrentUrl() {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
return window.location?.href || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const pages = getCurrentPages()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
return currentPage.route || ''
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户代理信息
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getUserAgent() {
|
|
||||||
if (typeof navigator !== 'undefined') {
|
|
||||||
return navigator.userAgent || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
|
||||||
return `${systemInfo.platform} ${systemInfo.system} ${systemInfo.model}`
|
|
||||||
} catch (error) {
|
|
||||||
return 'Unknown Device'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Unknown Device'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化错误对象
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_serializeError(error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return {
|
|
||||||
name: error.name,
|
|
||||||
message: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof error === 'object' && error !== null) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(error, null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送错误到webhook
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _sendErrorToWebhook(errorInfo, retryCount = 0, forceSend = false) {
|
|
||||||
// 环境检查:只在生产环境下发送错误信息
|
|
||||||
if (!forceSend && !this._isProduction() && !this.config?.forceEnable) {
|
|
||||||
console.info('非生产环境,错误信息不上报到webhook:', errorInfo.type)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const webhookUrl = import.meta.env.VITE_WEBHOOK
|
|
||||||
if (!webhookUrl) {
|
|
||||||
console.error('未配置webhook地址,无法发送错误信息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 格式化错误信息
|
|
||||||
const message = this._formatErrorMessage(errorInfo)
|
|
||||||
|
|
||||||
// 使用uni.request发送POST请求(适配uniapp环境)
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: webhookUrl,
|
|
||||||
method: 'POST',
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
msgtype: 'text',
|
|
||||||
text: {
|
|
||||||
content: message,
|
|
||||||
mentioned_list: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
success: resolve,
|
|
||||||
fail: reject,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
console.log('错误信息已发送到webhook')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('发送错误到webhook失败:', error)
|
|
||||||
|
|
||||||
// 重试机制
|
|
||||||
if (retryCount < (this.config?.maxRetries || 3)) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this._sendErrorToWebhook(errorInfo, retryCount + 1)
|
|
||||||
}, (this.config?.retryDelay || 1000) * (retryCount + 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载项目信息
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_loadProjectInfo() {
|
|
||||||
try {
|
|
||||||
if (uni.getSystemInfoSync()) {
|
|
||||||
const AppInfo = uni.getSystemInfoSync()
|
|
||||||
this.projectInfo.name = AppInfo.appName
|
|
||||||
this.projectInfo.version = AppInfo.appVersion
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 如果加载失败,使用默认信息
|
|
||||||
console.warn('无法加载项目信息,使用默认值')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化错误消息
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_formatErrorMessage(errorInfo) {
|
|
||||||
const timestamp = new Date(errorInfo.timestamp).toLocaleString('zh-CN')
|
|
||||||
|
|
||||||
let message = `🚨 JavaScript错误报告\n`
|
|
||||||
message += `📦 项目: ${this.projectInfo.name}\n`
|
|
||||||
message += `🏷️ 版本: ${this.projectInfo.version}\n`
|
|
||||||
message += `⏰ 时间: ${timestamp}\n`
|
|
||||||
message += `📱 页面: ${errorInfo.page || '未知页面'}\n`
|
|
||||||
message += `🌐 链接: ${errorInfo.url || '未知链接'}\n\n`
|
|
||||||
|
|
||||||
switch (errorInfo.type) {
|
|
||||||
case 'global':
|
|
||||||
message += `🔍 错误类型: 全局错误\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.message}\n`
|
|
||||||
if (errorInfo.source) {
|
|
||||||
message += `📂 文件: ${errorInfo.source}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.lineno) {
|
|
||||||
message += `📍 行号: ${errorInfo.lineno}:${errorInfo.colno}\n`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'promise':
|
|
||||||
message += `🔍 错误类型: Promise错误\n`
|
|
||||||
message += `📝 错误信息: ${this._serializeError(errorInfo.reason)}\n`
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'console':
|
|
||||||
message += `🔍 错误类型: Console错误\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.args.join(' ')}\n`
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'miniProgram':
|
|
||||||
message += `🔍 错误类型: 小程序错误\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.error || 'Unknown'}\n`
|
|
||||||
if (errorInfo.path) {
|
|
||||||
message += `📱 页面路径: ${errorInfo.path}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.query) {
|
|
||||||
message += `🔗 查询参数: ${errorInfo.query}\n`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'network':
|
|
||||||
message += `🔍 错误类型: 网络错误\n`
|
|
||||||
message += `📝 请求地址: ${errorInfo.url || 'Unknown'}\n`
|
|
||||||
message += `📝 请求方法: ${errorInfo.method || 'Unknown'}\n`
|
|
||||||
message += `📝 错误信息: ${this._serializeError(errorInfo.error)}\n`
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
message += `🔍 错误类型: ${errorInfo.type}\n`
|
|
||||||
message += `📝 错误信息: ${this._serializeError(errorInfo.error)}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
message += `\n📊 统计信息:\n`
|
|
||||||
message += `总计错误: ${this.errorStats.total}\n`
|
|
||||||
message += `全局错误: ${this.errorStats.global}\n`
|
|
||||||
message += `Promise错误: ${this.errorStats.promise}\n`
|
|
||||||
message += `Console错误: ${this.errorStats.console}\n`
|
|
||||||
message += `小程序错误: ${this.errorStats.miniProgram}\n`
|
|
||||||
|
|
||||||
// 添加设备信息
|
|
||||||
if (errorInfo.userAgent) {
|
|
||||||
message += `\n📱 设备信息:\n${errorInfo.userAgent}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前页面名称
|
|
||||||
* @returns {string} 页面名称
|
|
||||||
*/
|
|
||||||
function getCurrentPageName() {
|
|
||||||
try {
|
|
||||||
// 尝试从getCurrentPages获取
|
|
||||||
const pages = getCurrentPages()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
return currentPage.route || currentPage.$page?.fullPath || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,返回默认值
|
|
||||||
}
|
|
||||||
|
|
||||||
// 微信小程序环境
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const currentPages = getCurrentPages?.()
|
|
||||||
if (currentPages && currentPages.length > 0) {
|
|
||||||
return currentPages[currentPages.length - 1]?.route || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web环境
|
|
||||||
try {
|
|
||||||
if (typeof window !== 'undefined' && window.location) {
|
|
||||||
return window.location.pathname || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
|
|
||||||
return '未知页面'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建单例并导出
|
// 创建单例并导出
|
||||||
|
|||||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -7,6 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "*",
|
"dayjs": "*",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
|
"uniapp-error-monitor": "^1.0.0",
|
||||||
"vue": "^3.5.21"
|
"vue": "^3.5.21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -274,6 +275,24 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uniapp-error-monitor": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uniapp-error-monitor/-/uniapp-error-monitor-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-B2dyJV8Sx9W6k/J1vg/dKf7Tsbzs50tGrVz/Qm+ruMbZ/3u0oIzvjgrnF96hdlJx21JUgWt5cigfOaTyx7qfmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0",
|
||||||
|
"npm": ">=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@dcloudio/uni-app": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@dcloudio/uni-app": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.25",
|
"version": "3.5.25",
|
||||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz",
|
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^17.2.2",
|
|
||||||
"dayjs": "*",
|
"dayjs": "*",
|
||||||
|
"dotenv": "^17.2.2",
|
||||||
|
"uniapp-error-monitor": "^1.0.0",
|
||||||
"vue": "^3.5.21"
|
"vue": "^3.5.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user