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:
2026-05-23 23:52:39 +08:00
parent d1a0018d1b
commit 9838e7626b
20 changed files with 1073 additions and 78 deletions
+5
View File
@@ -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' })
+76
View File
@@ -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,