Merge branch 'feature_bugfix/20260116_V2' of https://git.valuefrontier.cn/vf/vf_react into feature_bugfix/20260116_V2

This commit is contained in:
2026-01-22 11:57:01 +08:00
2 changed files with 89 additions and 15 deletions

View File

@@ -20,11 +20,16 @@ REACT_APP_API_URL=https://api.valuefrontier.cn
# PostHog 分析配置(生产环境)
# PostHog API Key从 PostHog 项目设置中获取)
REACT_APP_POSTHOG_KEY=phc_xKlRyG69Bx7hgOdFeCeLUvQWvSjw18ZKFgCwCeYezWF
# PostHog API Host使用 PostHog Cloud
REACT_APP_POSTHOG_HOST=https://app.posthog.com
# PostHog API Host自托管实例
REACT_APP_POSTHOG_HOST=http://101.43.133.214:7050
# 启用会话录制Session Recording用于回放用户操作、排查问题
REACT_APP_ENABLE_SESSION_RECORDING=true
# PostHog Cloud 双写配置(验证自托管有效性 + Cloud 兜底)
REACT_APP_POSTHOG_CLOUD_KEY=phc_xxxxxxx
REACT_APP_POSTHOG_CLOUD_HOST=https://us.posthog.com
REACT_APP_POSTHOG_DUAL_WRITE=true
# React 构建优化配置
# 禁用 source map 生成(生产环境不需要,提升打包速度和安全性)
GENERATE_SOURCEMAP=false

View File

@@ -8,6 +8,7 @@ let isInitialized = false;
/**
* Initialize PostHog SDK
* Should be called once when the app starts
* 支持双写模式:同时发送到自托管和 Cloud
*/
export const initPostHog = () => {
// 开发环境禁用 PostHog减少日志噪音仅生产环境启用
@@ -26,6 +27,7 @@ export const initPostHog = () => {
const apiKey = process.env.REACT_APP_POSTHOG_KEY;
const apiHost = process.env.REACT_APP_POSTHOG_HOST || 'https://app.posthog.com';
const dualWrite = process.env.REACT_APP_POSTHOG_DUAL_WRITE === 'true';
if (!apiKey) {
console.warn('⚠️ PostHog API key not found. Analytics will be disabled.');
@@ -35,6 +37,7 @@ export const initPostHog = () => {
isInitializing = true;
try {
// 主实例(自托管)
posthog.init(apiKey, {
api_host: apiHost,
@@ -94,6 +97,26 @@ export const initPostHog = () => {
},
});
// Cloud 实例(双写备份)
if (dualWrite) {
const cloudKey = process.env.REACT_APP_POSTHOG_CLOUD_KEY;
const cloudHost = process.env.REACT_APP_POSTHOG_CLOUD_HOST || 'https://us.posthog.com';
if (cloudKey && cloudKey !== 'phc_xxxxxxx') {
posthog.init(cloudKey, {
api_host: cloudHost,
capture_pageview: true,
capture_pageleave: true,
autocapture: true,
session_recording: { enabled: false }, // Cloud 不需要录屏,节省流量
}, 'cloud'); // 第三个参数是实例名称
console.log('[PostHog] 双写模式已启用:自托管 + Cloud');
} else {
console.warn('[PostHog] 双写模式已启用但 Cloud API Key 未配置');
}
}
isInitialized = true;
} catch (error) {
// 忽略 AbortError通常由热重载或快速导航引起
@@ -116,6 +139,7 @@ export const getPostHog = () => {
/**
* Identify user with PostHog
* Call this after successful login/registration
* 双写模式下同时同步到 Cloud
*
* @param {string} userId - Unique user identifier
* @param {object} userProperties - User properties (email, name, subscription_tier, etc.)
@@ -126,16 +150,31 @@ export const identifyUser = (userId, userProperties = {}) => {
return;
}
const properties = {
email: userProperties.email,
username: userProperties.username,
subscription_tier: userProperties.subscription_tier || 'free',
role: userProperties.role,
registration_date: userProperties.registration_date,
last_login: new Date().toISOString(),
...userProperties,
};
try {
posthog.identify(userId, {
email: userProperties.email,
username: userProperties.username,
subscription_tier: userProperties.subscription_tier || 'free',
role: userProperties.role,
registration_date: userProperties.registration_date,
last_login: new Date().toISOString(),
...userProperties,
});
// 主实例
posthog.identify(userId, properties);
// 双写到 Cloud
if (process.env.REACT_APP_POSTHOG_DUAL_WRITE === 'true') {
try {
const cloudInstance = posthog.getInstance('cloud');
if (cloudInstance) {
cloudInstance.identify(userId, properties);
}
} catch (e) {
// 静默失败,不影响主流程
}
}
} catch (error) {
console.error('❌ User identification failed:', error);
}
@@ -157,16 +196,32 @@ export const setUserProperties = (properties) => {
/**
* Track custom event
* 双写模式下同时发送到 Cloud
*
* @param {string} eventName - Name of the event
* @param {object} properties - Event properties
*/
export const trackEvent = (eventName, properties = {}) => {
const eventProperties = {
...properties,
timestamp: new Date().toISOString(),
};
try {
posthog.capture(eventName, {
...properties,
timestamp: new Date().toISOString(),
});
// 主实例
posthog.capture(eventName, eventProperties);
// 双写到 Cloud
if (process.env.REACT_APP_POSTHOG_DUAL_WRITE === 'true') {
try {
const cloudInstance = posthog.getInstance('cloud');
if (cloudInstance) {
cloudInstance.capture(eventName, eventProperties);
}
} catch (e) {
// 静默失败,不影响主流程
}
}
} catch (error) {
console.error('❌ Event tracking failed:', error);
}
@@ -220,10 +275,24 @@ export const trackPageView = (pagePath, properties = {}) => {
/**
* Reset user session
* Call this on logout
* 双写模式下同时重置 Cloud 实例
*/
export const resetUser = () => {
try {
// 主实例
posthog.reset();
// 双写:重置 Cloud 实例
if (process.env.REACT_APP_POSTHOG_DUAL_WRITE === 'true') {
try {
const cloudInstance = posthog.getInstance('cloud');
if (cloudInstance) {
cloudInstance.reset();
}
} catch (e) {
// 静默失败
}
}
} catch (error) {
console.error('❌ Session reset failed:', error);
}