1. ✅ 集中式状态管理 - PostHog 状态与应用状态统一管理 2. ✅ 自动追踪机制 - Middleware 自动拦截 Redux actions 进行追踪 3. ✅ Redux DevTools 支持 - 可视化调试所有 PostHog 事件 4. ✅ 离线事件缓存 - 网络恢复时自动刷新缓存事件 5. ✅ 性能优化 Hooks - 提供轻量级 Hook 避免不必要的重渲染
282 lines
8.5 KiB
JavaScript
282 lines
8.5 KiB
JavaScript
// src/store/middleware/posthogMiddleware.js
|
||
import { trackPageView } from '../../lib/posthog';
|
||
import { trackEvent } from '../slices/posthogSlice';
|
||
import { logger } from '../../utils/logger';
|
||
import {
|
||
ACTIVATION_EVENTS,
|
||
RETENTION_EVENTS,
|
||
SPECIAL_EVENTS,
|
||
REVENUE_EVENTS,
|
||
} from '../../lib/constants';
|
||
|
||
// ==================== 自动追踪规则配置 ====================
|
||
|
||
/**
|
||
* Action 到 PostHog 事件的映射
|
||
* 当这些 Redux actions 被 dispatch 时,自动追踪对应的 PostHog 事件
|
||
*/
|
||
const ACTION_TO_EVENT_MAP = {
|
||
// ==================== 登录/登出 ====================
|
||
'auth/login/fulfilled': {
|
||
event: ACTIVATION_EVENTS.USER_LOGGED_IN,
|
||
getProperties: (action) => ({
|
||
login_method: action.payload?.login_method || 'unknown',
|
||
user_id: action.payload?.user?.id,
|
||
}),
|
||
},
|
||
'auth/logout': {
|
||
event: SPECIAL_EVENTS.USER_LOGGED_OUT,
|
||
getProperties: () => ({}),
|
||
},
|
||
'auth/wechatLogin/fulfilled': {
|
||
event: ACTIVATION_EVENTS.USER_LOGGED_IN,
|
||
getProperties: (action) => ({
|
||
login_method: 'wechat',
|
||
user_id: action.payload?.user?.id,
|
||
}),
|
||
},
|
||
|
||
// ==================== Community/新闻模块 ====================
|
||
'communityData/fetchHotEvents/fulfilled': {
|
||
event: RETENTION_EVENTS.NEWS_LIST_VIEWED,
|
||
getProperties: (action) => ({
|
||
event_count: action.payload?.length || 0,
|
||
source: 'community_page',
|
||
}),
|
||
},
|
||
'communityData/fetchPopularKeywords/fulfilled': {
|
||
event: RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED,
|
||
getProperties: () => ({
|
||
feature: 'popular_keywords',
|
||
}),
|
||
},
|
||
|
||
// ==================== 搜索 ====================
|
||
'search/submit': {
|
||
event: RETENTION_EVENTS.SEARCH_QUERY_SUBMITTED,
|
||
getProperties: (action) => ({
|
||
query: action.payload?.query,
|
||
category: action.payload?.category,
|
||
}),
|
||
},
|
||
'search/filterApplied': {
|
||
event: RETENTION_EVENTS.SEARCH_FILTER_APPLIED,
|
||
getProperties: (action) => ({
|
||
filter_type: action.payload?.filterType,
|
||
filter_value: action.payload?.filterValue,
|
||
}),
|
||
},
|
||
|
||
// ==================== 支付/订阅 ====================
|
||
'payment/initiated': {
|
||
event: REVENUE_EVENTS.PAYMENT_INITIATED,
|
||
getProperties: (action) => ({
|
||
amount: action.payload?.amount,
|
||
payment_method: action.payload?.method,
|
||
subscription_tier: action.payload?.tier,
|
||
}),
|
||
},
|
||
'payment/success': {
|
||
event: REVENUE_EVENTS.PAYMENT_SUCCESSFUL,
|
||
getProperties: (action) => ({
|
||
amount: action.payload?.amount,
|
||
transaction_id: action.payload?.transactionId,
|
||
subscription_tier: action.payload?.tier,
|
||
}),
|
||
},
|
||
'subscription/upgraded': {
|
||
event: REVENUE_EVENTS.SUBSCRIPTION_UPGRADED,
|
||
getProperties: (action) => ({
|
||
from_tier: action.payload?.fromTier,
|
||
to_tier: action.payload?.toTier,
|
||
}),
|
||
},
|
||
|
||
// ==================== 错误追踪 ====================
|
||
'error/occurred': {
|
||
event: SPECIAL_EVENTS.ERROR_OCCURRED,
|
||
getProperties: (action) => ({
|
||
error_type: action.payload?.errorType,
|
||
error_message: action.payload?.message,
|
||
stack_trace: action.payload?.stack,
|
||
}),
|
||
},
|
||
'api/error': {
|
||
event: SPECIAL_EVENTS.API_ERROR,
|
||
getProperties: (action) => ({
|
||
endpoint: action.payload?.endpoint,
|
||
status_code: action.payload?.statusCode,
|
||
error_message: action.payload?.message,
|
||
}),
|
||
},
|
||
};
|
||
|
||
// ==================== 页面路由追踪配置 ====================
|
||
|
||
/**
|
||
* 路由变化的 action type(根据不同路由库调整)
|
||
*/
|
||
const LOCATION_CHANGE_ACTIONS = [
|
||
'@@router/LOCATION_CHANGE', // Redux-first router
|
||
'router/navigate', // 自定义路由 action
|
||
];
|
||
|
||
/**
|
||
* 根据路径识别页面类型
|
||
*/
|
||
const getPageTypeFromPath = (pathname) => {
|
||
if (pathname === '/home' || pathname === '/') {
|
||
return { page_type: 'landing' };
|
||
} else if (pathname.startsWith('/auth/')) {
|
||
return { page_type: 'auth' };
|
||
} else if (pathname.startsWith('/community')) {
|
||
return { page_type: 'feature', feature_name: 'community' };
|
||
} else if (pathname.startsWith('/concepts')) {
|
||
return { page_type: 'feature', feature_name: 'concepts' };
|
||
} else if (pathname.startsWith('/stocks')) {
|
||
return { page_type: 'feature', feature_name: 'stocks' };
|
||
} else if (pathname.startsWith('/limit-analyse')) {
|
||
return { page_type: 'feature', feature_name: 'limit_analyse' };
|
||
} else if (pathname.startsWith('/trading-simulation')) {
|
||
return { page_type: 'feature', feature_name: 'trading_simulation' };
|
||
} else if (pathname.startsWith('/company')) {
|
||
return { page_type: 'detail', content_type: 'company' };
|
||
} else if (pathname.startsWith('/event-detail')) {
|
||
return { page_type: 'detail', content_type: 'event' };
|
||
}
|
||
return { page_type: 'other' };
|
||
};
|
||
|
||
// ==================== 中间件实现 ====================
|
||
|
||
/**
|
||
* PostHog Middleware
|
||
* 自动拦截 Redux actions 并追踪对应的 PostHog 事件
|
||
*/
|
||
const posthogMiddleware = (store) => (next) => (action) => {
|
||
// 先执行 action
|
||
const result = next(action);
|
||
|
||
// 获取当前 PostHog 状态
|
||
const state = store.getState();
|
||
const posthogState = state.posthog;
|
||
|
||
// 如果 PostHog 未初始化,不追踪(事件会被缓存到 eventQueue)
|
||
if (!posthogState?.isInitialized) {
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
// ==================== 1. 自动追踪特定 actions ====================
|
||
if (ACTION_TO_EVENT_MAP[action.type]) {
|
||
const { event, getProperties } = ACTION_TO_EVENT_MAP[action.type];
|
||
const properties = getProperties(action);
|
||
|
||
// 通过 dispatch 追踪事件(会走 Redux 状态管理)
|
||
store.dispatch(trackEvent({ eventName: event, properties }));
|
||
|
||
logger.debug('PostHog Middleware', `自动追踪事件: ${event}`, properties);
|
||
}
|
||
|
||
// ==================== 2. 路由变化追踪 ====================
|
||
if (LOCATION_CHANGE_ACTIONS.includes(action.type)) {
|
||
const location = action.payload?.location || action.payload;
|
||
const pathname = location?.pathname || window.location.pathname;
|
||
const search = location?.search || window.location.search;
|
||
|
||
// 识别页面类型
|
||
const pageProperties = getPageTypeFromPath(pathname);
|
||
|
||
// 追踪页面浏览
|
||
trackPageView(pathname, {
|
||
...pageProperties,
|
||
page_path: pathname,
|
||
page_search: search,
|
||
page_title: document.title,
|
||
referrer: document.referrer,
|
||
});
|
||
|
||
logger.debug('PostHog Middleware', `页面浏览追踪: ${pathname}`, pageProperties);
|
||
}
|
||
|
||
// ==================== 3. 离线事件处理 ====================
|
||
// 检测网络状态变化
|
||
if (action.type === 'network/online') {
|
||
// 恢复在线时,刷新缓存的事件
|
||
const { eventQueue } = posthogState;
|
||
if (eventQueue && eventQueue.length > 0) {
|
||
logger.info('PostHog Middleware', `网络恢复,刷新 ${eventQueue.length} 个缓存事件`);
|
||
// 这里可以 dispatch flushCachedEvents,但为了避免循环依赖,直接在 slice 中处理
|
||
}
|
||
}
|
||
|
||
// ==================== 4. 性能追踪(可选) ====================
|
||
// 追踪耗时较长的 actions
|
||
const startTime = action.meta?.startTime;
|
||
if (startTime) {
|
||
const duration = Date.now() - startTime;
|
||
if (duration > 1000) {
|
||
// 超过 1 秒的操作
|
||
store.dispatch(trackEvent({
|
||
eventName: SPECIAL_EVENTS.PAGE_LOAD_TIME,
|
||
properties: {
|
||
action_type: action.type,
|
||
duration_ms: duration,
|
||
is_slow: true,
|
||
},
|
||
}));
|
||
}
|
||
}
|
||
} catch (error) {
|
||
logger.error('PostHog Middleware', '追踪失败', error, { actionType: action.type });
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
// ==================== 工具函数 ====================
|
||
|
||
/**
|
||
* 创建带性能追踪的 action creator
|
||
* 用法: dispatch(withTiming(someAction(payload)))
|
||
*/
|
||
export const withTiming = (action) => ({
|
||
...action,
|
||
meta: {
|
||
...action.meta,
|
||
startTime: Date.now(),
|
||
},
|
||
});
|
||
|
||
/**
|
||
* 手动触发页面浏览追踪
|
||
* 用于非路由跳转的场景(如 Modal、Tab 切换)
|
||
*/
|
||
export const trackModalView = (modalName, properties = {}) => (dispatch) => {
|
||
dispatch(trackEvent({
|
||
eventName: '$pageview',
|
||
properties: {
|
||
modal_name: modalName,
|
||
page_type: 'modal',
|
||
...properties,
|
||
},
|
||
}));
|
||
};
|
||
|
||
/**
|
||
* 追踪 Tab 切换
|
||
*/
|
||
export const trackTabChange = (tabName, properties = {}) => (dispatch) => {
|
||
dispatch(trackEvent({
|
||
eventName: RETENTION_EVENTS.NEWS_TAB_CLICKED,
|
||
properties: {
|
||
tab_name: tabName,
|
||
...properties,
|
||
},
|
||
}));
|
||
};
|
||
|
||
// ==================== Export ====================
|
||
|
||
export default posthogMiddleware;
|