feat: 分析模块、接口管理及其他功能优化
- 后端: WebMvcConfig/拦截器/AnalyticsService/Mapper/测试优化,新增 Knife4jConfig、AnalyticsDictionary、数据库迁移脚本 - 前端: 分析仪表盘 UI 优化、接口管理列表及详情测试面板 - 小程序: analytics 服务优化、request 增强 - 文档: 分析模块中文标签设计文档、品牌重命名设计文档 - 部署: conf 配置优化、deploy.py 脚本更新 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@ let pageEnterAt = {}
|
||||
let flushTimer = null
|
||||
let initialized = false
|
||||
|
||||
const runtimeRoot = typeof globalThis !== 'undefined' ? globalThis : {}
|
||||
|
||||
const now = () => Date.now()
|
||||
|
||||
const uuid = (prefix) => `${prefix}_${now()}_${Math.random().toString(36).slice(2, 10)}`
|
||||
@@ -60,6 +62,9 @@ const safeProperties = (properties = {}) => {
|
||||
export const initAnalytics = () => {
|
||||
if (initialized) return
|
||||
initialized = true
|
||||
runtimeRoot.__emotionAnalyticsTrack = (eventName, properties = {}, options = {}) => {
|
||||
track(eventName, properties, options)
|
||||
}
|
||||
sessionId = uuid('session')
|
||||
loadQueue()
|
||||
track('app_launch', {}, { eventType: 'app' })
|
||||
|
||||
@@ -3,9 +3,74 @@ import { getEnvValue, getConfig } from '../config/env.js'
|
||||
const API_BASE_URL = getEnvValue('API_BASE_URL')
|
||||
|
||||
const AUTH_PATH_PREFIX = '/auth/'
|
||||
const ANALYTICS_BATCH_PATH = '/analytics/events/batch'
|
||||
|
||||
const isAuthPath = (path = '') => path.startsWith(AUTH_PATH_PREFIX)
|
||||
|
||||
const API_LABELS = {
|
||||
'/auth/login': '登录',
|
||||
'/auth/refresh': '刷新登录状态',
|
||||
'/user-profile/create': '创建用户画像',
|
||||
'/user-profile/update': '更新用户画像',
|
||||
'/lifeEvent/list': '查询人生事件',
|
||||
'/lifeEvent/detail': '查看人生事件',
|
||||
'/lifeEvent/create': '创建人生事件',
|
||||
'/lifeEvent/update': '更新人生事件',
|
||||
'/lifeEvent/delete': '删除人生事件',
|
||||
'/lifePath/list': '查询实现路径',
|
||||
'/lifePath/create': '创建实现路径',
|
||||
'/lifePath/update': '更新实现路径',
|
||||
'/epic-script/list': '查询我的剧本',
|
||||
'/epic-script/detail': '查看剧本详情',
|
||||
'/epic-script/create': '保存生成剧本',
|
||||
'/epic-script/update': '更新剧本',
|
||||
'/ai/runtime/stream': 'AI 流式生成',
|
||||
'/asr/recognize': '语音识别',
|
||||
'/tts/tasks': '创建朗读任务',
|
||||
'/tts/tasks/by-source': '查询内容朗读任务',
|
||||
'/social-import': '社交数据导入'
|
||||
}
|
||||
|
||||
const normalizePath = (path = '') => {
|
||||
const queryIndex = path.indexOf('?')
|
||||
return queryIndex >= 0 ? path.slice(0, queryIndex) : path
|
||||
}
|
||||
|
||||
const apiLabel = (path = '') => {
|
||||
const normalized = normalizePath(path)
|
||||
if (API_LABELS[normalized]) return API_LABELS[normalized]
|
||||
const matched = Object.keys(API_LABELS).find(key => normalized.startsWith(`${key}/`))
|
||||
return matched ? API_LABELS[matched] : normalized || '未知接口'
|
||||
}
|
||||
|
||||
const currentPagePath = () => {
|
||||
try {
|
||||
const pages = getCurrentPages?.() || []
|
||||
const current = pages[pages.length - 1]
|
||||
return current?.route ? `/${current.route}` : ''
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const trackApiRequest = (eventName, options, meta = {}) => {
|
||||
const normalized = normalizePath(options.url || '')
|
||||
if (!normalized || normalized === ANALYTICS_BATCH_PATH) return
|
||||
const hook = typeof globalThis !== 'undefined' ? globalThis.__emotionAnalyticsTrack : null
|
||||
if (typeof hook !== 'function') return
|
||||
hook(eventName, {
|
||||
api_path: normalized,
|
||||
api_label: apiLabel(normalized),
|
||||
method: options.method || 'GET',
|
||||
status_code: meta.statusCode || 0,
|
||||
duration_ms: meta.durationMs || 0
|
||||
}, {
|
||||
eventType: 'api',
|
||||
pagePath: currentPagePath(),
|
||||
durationMs: meta.durationMs
|
||||
})
|
||||
}
|
||||
|
||||
const createRequestError = (message, meta = {}) => {
|
||||
const error = new Error(message || 'Request failed')
|
||||
Object.assign(error, meta)
|
||||
@@ -52,6 +117,7 @@ const request = (options) => {
|
||||
const method = options.method || 'GET'
|
||||
const fullUrl = `${API_BASE_URL}${options.url}`
|
||||
const forceLog = isAuthPath(options.url)
|
||||
const startedAt = Date.now()
|
||||
|
||||
logApi('log', 'request', {
|
||||
method,
|
||||
@@ -70,6 +136,11 @@ const request = (options) => {
|
||||
success: (res) => {
|
||||
const { data, statusCode } = res
|
||||
const success = statusCode >= 200 && statusCode < 300 && (data?.code === 200 || data?.code === 0)
|
||||
const durationMs = Date.now() - startedAt
|
||||
trackApiRequest(success ? 'api_request_success' : 'api_request_fail', { ...options, method }, {
|
||||
statusCode,
|
||||
durationMs
|
||||
})
|
||||
|
||||
logApi('log', 'response', {
|
||||
path: options.url,
|
||||
@@ -106,6 +177,11 @@ const request = (options) => {
|
||||
}))
|
||||
},
|
||||
fail: (err) => {
|
||||
const durationMs = Date.now() - startedAt
|
||||
trackApiRequest('api_request_fail', { ...options, method }, {
|
||||
statusCode: 0,
|
||||
durationMs
|
||||
})
|
||||
logApi('error', 'fail', {
|
||||
path: options.url,
|
||||
url: fullUrl,
|
||||
|
||||
Reference in New Issue
Block a user