浏览代码

调整数据埋点的逻辑

zxlie 5 月之前
父节点
当前提交
638e96b890
共有 8 个文件被更改,包括 181 次插入260 次删除
  1. 12 7
      .cursorrules
  2. 11 1
      apps/background/background.js
  3. 101 78
      apps/background/statistics.js
  4. 53 0
      server/.cursorrules
  5. 4 0
      server/.gitignore
  6. 0 78
      server/GA4使用说明.md
  7. 0 74
      server/ga4-proxy.js
  8. 0 22
      server/package.json

+ 12 - 7
.cursorrules

@@ -4,13 +4,18 @@
 - 你能根据README.md文件中的插件描述,读懂插件中包含的各个工具的功能,并能根据描述,给出工具的实现方案。
 
 # 项目结构规范
-- apps/目录是项目的主目录
-- 每个功能模块都是apps/下的独立目录
-- manifest.json 是扩展的配置文件
-- background/ 目录包含后台服务脚本
-- popup/ 目录包含扩展的弹出窗口页面
-- options/ 目录包含扩展的配置页面
-- static/ 目录包含静态资源
+- apps/目录是项目的主目录,插件的每个功能模块都是apps/下的独立目录
+    - manifest.json 是扩展的配置文件
+    - background/ 目录包含后台服务脚本
+    - popup/ 目录包含扩展的弹出窗口页面
+    - options/ 目录包含扩展的配置页面
+    - static/ 目录包含静态资源
+- server/目录是后端服务目录,用于统计数据,以及提供数据分析的管理后台
+    - 管理后台的整体功能应该尽量参考市面上同类产品的管理后台,如百度统计、Google Analytics、友盟等
+    - 管理后台的整体UI风格要采用TailWind CSS风格
+    - 数据统计分析的维度,都要参考apps/background/statistics.js中的实现,以及在background.js中调用
+    - 收集的数据统一存储在MongoDB中,MongoDB的连接字符串在server/config.js中配置
+
 
 # 编码规范
 - 模块化开发,每个功能保持独立

+ 11 - 1
apps/background/background.js

@@ -572,8 +572,18 @@ let BgPageInstance = (function () {
                     break;
             }
         });
+        
         // 卸载
-        chrome.runtime.setUninstallURL(chrome.runtime.getManifest().homepage_url);
+        chrome.runtime.setUninstallURL(chrome.runtime.getManifest().homepage_url, () => {
+            // 记录卸载事件
+            try {
+                import('./statistics.js').then(({default: Statistics}) => {
+                    Statistics.recordUninstall();
+                });
+            } catch (e) {
+                // 忽略异常
+            }
+        });
     };
 
     /**

+ 101 - 78
apps/background/statistics.js

@@ -1,13 +1,18 @@
 /**
- * FeHelper数据统计模块 - 使用GA4实现
+ * FeHelper数据统计模块
  * @author fehelper
  */
 
 import Awesome from './awesome.js';
 
-// GA4测量ID - 需要替换为您自己的GA4测量ID
-const GA4_MEASUREMENT_ID = 'G-1NWRCJRT01';
-const GA4_API_SECRET = 'wHIo3W6uRRCvhZ18hwOmiA';
+// 数据上报服务器地址
+let manifest = chrome.runtime.getManifest();
+let SERVER_TRACK_URL = '';
+if (manifest.name && manifest.name.endsWith('-Dev')) {
+    SERVER_TRACK_URL = 'https://chrome.fehelper.com/api/track';
+} else {
+    SERVER_TRACK_URL = 'https://localhost:3001/api/track';
+}
 
 // 用户ID存储键名
 const USER_ID_KEY = 'FH_USER_ID';
@@ -16,6 +21,9 @@ const LAST_ACTIVE_DATE_KEY = 'FH_LAST_ACTIVE_DATE';
 // 用户日常使用数据存储键名
 const USER_USAGE_DATA_KEY = 'FH_USER_USAGE_DATA';
 
+// 记录background启动时间
+const FH_TIME_OPENED = Date.now();
+
 let Statistics = (function() {
     
     // 用户唯一标识
@@ -88,69 +96,90 @@ let Statistics = (function() {
     };
     
     /**
-     * 使用GA4发送事件数据
+     * 获取客户端详细信息,对标百度统计/GA/友盟
+     * @returns {Object}
+     */
+    const getClientInfo = () => {
+        const nav = navigator;
+        const screenInfo = window.screen;
+        const lang = nav.language || nav.userLanguage || '';
+        const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
+        const ua = nav.userAgent;
+        const platform = nav.platform;
+        const vendor = nav.vendor;
+        const colorDepth = screenInfo.colorDepth;
+        const screenWidth = screenInfo.width;
+        const screenHeight = screenInfo.height;
+        const deviceMemory = nav.deviceMemory || '';
+        const hardwareConcurrency = nav.hardwareConcurrency || '';
+        // 网络信息
+        const connection = nav.connection || nav.mozConnection || nav.webkitConnection || {};
+        // 屏幕方向
+        const screenOrientation = screenInfo.orientation ? screenInfo.orientation.type : '';
+        // 触摸支持
+        const touchSupport = ('ontouchstart' in window) || (nav.maxTouchPoints > 0);
+        // JS堆内存(仅Chrome)
+        let memoryJSHeapSize = '';
+        if (window.performance && window.performance.memory) {
+            memoryJSHeapSize = window.performance.memory.jsHeapSizeLimit;
+        }
+        return {
+            language: lang,
+            timezone,
+            userAgent: ua,
+            platform,
+            vendor,
+            colorDepth,
+            screenWidth,
+            screenHeight,
+            deviceMemory,
+            hardwareConcurrency,
+            extensionVersion: chrome.runtime.getManifest().version,
+            // 新增字段
+            networkType: connection.effectiveType || '',
+            downlink: connection.downlink || '',
+            rtt: connection.rtt || '',
+            online: nav.onLine,
+            touchSupport,
+            cookieEnabled: nav.cookieEnabled,
+            doNotTrack: nav.doNotTrack,
+            appVersion: nav.appVersion,
+            appName: nav.appName,
+            product: nav.product,
+            vendorSub: nav.vendorSub,
+            screenOrientation,
+            memoryJSHeapSize,
+            timeOpened: FH_TIME_OPENED
+        };
+    };
+
+    /**
+     * 使用自建服务器发送事件数据
      * @param {string} eventName - 事件名称
      * @param {Object} params - 事件参数
      */
-    const sendToGA4 = async (eventName, params = {}) => {
-        // 获取设备和浏览器信息
-        const manifest = chrome.runtime.getManifest();
-        
-        // 确保获取用户ID
+    const sendToServer = async (eventName, params = {}) => {
         const uid = await getUserId();
-        
-        // 构建GA4所需参数
-        const gaParams = {
-            client_id: uid,
-            user_id: uid,
-            non_personalized_ads: true,
+        const clientInfo = getClientInfo();
+        const payload = {
+            event: eventName,
+            userId: uid,
+            date: todayStr,
+            timestamp: Date.now(),
+            ...clientInfo,
             ...params
         };
-        
-        // GA4主URL
-        const mainURL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA4_MEASUREMENT_ID}&api_secret=${GA4_API_SECRET}`;
-        // 国内备用URL (可以使用自己的代理转发)
-        const backupURL = `https://chrome.fehelper.com/mp/collect?measurement_id=${GA4_MEASUREMENT_ID}&api_secret=${GA4_API_SECRET}`;
-        
-        // 准备发送的数据
-        const data = {
-            client_id: uid,
-            user_id: uid,
-            events: [
-                {
-                    name: eventName,
-                    params: {
-                        extension_version: manifest.version,
-                        ...gaParams
-                    }
-                }
-            ]
-        };
-        
         try {
-            // 主要尝试直接发送到GA
-            fetch(mainURL, {
+            fetch(SERVER_TRACK_URL, {
                 method: 'POST',
-                body: JSON.stringify(data),
-                keepalive: true,
+                body: JSON.stringify(payload),
                 headers: {
                     'Content-Type': 'application/json'
-                }
-            }).catch(error => {
-                // 如果主要GA服务器失败,尝试备用URL
-                if (backupURL) {
-                    fetch(backupURL, {
-                        method: 'POST',
-                        body: JSON.stringify(data),
-                        keepalive: true,
-                        headers: {
-                            'Content-Type': 'application/json'
-                        }
-                    }).catch(e => console.log('备用GA4统计服务器发送失败:', e));
-                }
-            });
+                },
+                keepalive: true
+            }).catch(e => console.log('自建统计服务器发送失败:', e));
         } catch (error) {
-            console.log('GA4统计发送失败:', error);
+            console.log('自建统计发送失败:', error);
         }
     };
     
@@ -175,8 +204,8 @@ let Statistics = (function() {
                     };
                 }
                 
-                // 发送每日活跃记录到GA4
-                sendToGA4('daily_active_user', {
+                // 发送每日活跃记录到自建服务器
+                sendToServer('daily_active_user', {
                     date: todayStr
                 });
             }
@@ -189,7 +218,7 @@ let Statistics = (function() {
      * 记录插件安装事件
      */
     const recordInstallation = async () => {
-        sendToGA4('extension_installed');
+        sendToServer('extension_installed');
     };
     
     /**
@@ -197,11 +226,18 @@ let Statistics = (function() {
      * @param {string} previousVersion - 更新前的版本
      */
     const recordUpdate = async (previousVersion) => {
-        sendToGA4('extension_updated', {
+        sendToServer('extension_updated', {
             previous_version: previousVersion
         });
     };
     
+    /**
+     * 记录插件卸载事件
+     */
+    const recordUninstall = async () => {
+        sendToServer('extension_uninstall');
+    };
+    
     /**
      * 记录工具使用情况
      * @param {string} toolName - 工具名称
@@ -230,8 +266,8 @@ let Statistics = (function() {
         // 保存使用数据
         await saveUsageData();
         
-        // 发送工具使用记录到GA4
-        sendToGA4('tool_used', {
+        // 发送工具使用记录到自建服务器
+        sendToServer('tool_used', {
             tool_name: toolName,
             date: todayStr
         });
@@ -250,7 +286,7 @@ let Statistics = (function() {
                 .slice(0, 5)
                 .map(([name, count]) => ({name, count}));
             
-            sendToGA4('usage_summary', {
+            sendToServer('usage_summary', {
                 top_tools: JSON.stringify(toolRanking)
             });
             
@@ -270,19 +306,6 @@ let Statistics = (function() {
         }, ONE_WEEK);
     };
     
-    /**
-     * 初始化GA4测量代码
-     */
-    const initGA4 = async () => {
-        // 获取用户ID
-        const uid = await getUserId();
-        
-        // 发送初始化事件
-        sendToGA4('extension_loaded', {
-            extension_version: chrome.runtime.getManifest().version
-        });
-    };
-    
     /**
      * 初始化统计模块
      */
@@ -290,7 +313,6 @@ let Statistics = (function() {
         await getUserId();
         await loadUsageData();
         await recordDailyActiveUser();
-        await initGA4();
         scheduleSyncStats();
     };
     
@@ -416,7 +438,8 @@ let Statistics = (function() {
         recordUpdate,
         recordToolUsage,
         getRecentUsedTools,
-        getDashboardData
+        getDashboardData,
+        recordUninstall
     };
 })();
 

+ 53 - 0
server/.cursorrules

@@ -0,0 +1,53 @@
+    # Role
+    你是一名精通Vue.js的高级全栈工程师,拥有20年的Web开发经验。你的任务是帮助一位不太懂技术的初中生用户完成Vue.js项目的开发。你的工作对用户来说非常重要,完成后将获得10000美元奖励。
+
+    # Goal
+    你的目标是以用户容易理解的方式帮助他们完成Vue.js项目的设计和开发工作。你应该主动完成所有工作,而不是等待用户多次推动你。
+
+    在理解用户需求、编写代码和解决问题时,你应始终遵循以下原则:
+
+    ## 第一步:项目初始化
+    - 当用户提出任何需求时,首先浏览项目根目录下的README.md文件和所有代码文档,理解项目目标、架构和实现方式。
+    - 如果还没有README文件,创建一个。这个文件将作为项目功能的说明书和你对项目内容的规划。
+    - 在README.md中清晰描述所有功能的用途、使用方法、参数说明和返回值说明,确保用户可以轻松理解和使用这些功能。
+
+    # 本规则由 AI进化论-花生 创建,版权所有,引用请注明出处
+
+    ## 第二步:需求分析和开发
+    ### 理解用户需求时:
+    - 充分理解用户需求,站在用户角度思考。
+    - 作为产品经理,分析需求是否存在缺漏,与用户讨论并完善需求。
+    - 选择最简单的解决方案来满足用户需求。
+
+    ### 编写代码时:
+    - 使用Vue 3的Composition API进行开发,合理使用setup语法糖。
+    - 遵循Vue.js的最佳实践和设计模式,如单文件组件(SFC)。
+    - 利用Vue Router进行路由管理,实现页面导航和路由守卫。
+    - 使用Pinia进行状态管理,合理组织store结构。
+    - 实现组件化开发,确保组件的可复用性和可维护性。
+    - 使用Vue的响应式系统,合理使用ref、reactive等响应式API。
+    - 实现响应式设计,确保在不同设备上的良好体验。
+    - 使用TypeScript进行类型检查,提高代码质量。
+    - 编写详细的代码注释,并在代码中添加必要的错误处理和日志记录。
+    - 合理使用Vue的生命周期钩子和组合式函数。
+
+    ### 解决问题时:
+    - 全面阅读相关代码文件,理解所有代码的功能和逻辑。
+    - 分析导致错误的原因,提出解决问题的思路。
+    - 与用户进行多次交互,根据反馈调整解决方案。
+    - 善用Vue DevTools进行调试和性能分析。
+    - 当一个bug经过两次调整仍未解决时,你将启动系统二思考模式:
+      1. 系统性分析bug产生的根本原因
+      2. 提出可能的假设
+      3. 设计验证假设的方法
+      4. 提供三种不同的解决方案,并详细说明每种方案的优缺点
+      5. 让用户根据实际情况选择最适合的方案
+
+    ## 第三步:项目总结和优化
+    - 完成任务后,反思完成步骤,思考项目可能存在的问题和改进方式。
+    - 更新README.md文件,包括新增功能说明和优化建议。
+    - 考虑使用Vue的高级特性,如Suspense、Teleport等来增强功能。
+    - 优化应用性能,包括代码分割、懒加载、虚拟列表等。
+    - 实现适当的错误边界处理和性能监控。
+
+    在整个过程中,始终参考[Vue.js官方文档](https://vuejs.org/guide/introduction.html),确保使用最新的Vue.js开发最佳实践。

+ 4 - 0
server/.gitignore

@@ -0,0 +1,4 @@
+node_modules/
+.env
+data/
+.DS_Store 

+ 0 - 78
server/GA4使用说明.md

@@ -1,78 +0,0 @@
-# FeHelper插件GA4数据统计使用指南
-
-本文档将指导您如何配置和使用Google Analytics 4 (GA4)来跟踪FeHelper浏览器扩展的使用情况。
-
-## 1. 创建GA4账户和配置
-
-### 1.1 创建GA4属性
-1. 访问[Google Analytics](https://analytics.google.com/)并登录您的Google账户
-2. 创建一个新的GA4属性(如果没有的话)
-3. 记录下您的测量ID(Measurement ID),通常格式为`G-XXXXXXXXXX`
-
-### 1.2 获取API密钥
-1. 在GA4管理界面中,进入`管理` > `数据流` > 选择您的网站/应用数据流
-2. 在数据流详情页面中,找到`测量ID`和`API密钥`部分
-3. 创建一个新的API密钥,设置一个容易记住的名称(如"FeHelper扩展")
-4. 记录下生成的API密钥,这将用于授权数据发送
-
-## 2. 配置FeHelper扩展使用GA4
-
-在`apps/background/statistics.js`文件中,更新以下配置:
-
-```javascript
-// GA4测量ID - 替换为您自己的GA4测量ID
-const GA4_MEASUREMENT_ID = 'G-XXXXXXXXXX';
-const GA4_API_SECRET = 'YOUR_API_SECRET';
-```
-
-请将`G-XXXXXXXXXX`替换为您的GA4测量ID,将`YOUR_API_SECRET`替换为您的API密钥。
-
-## 3. 解决中国大陆访问GA的问题
-
-由于中国大陆地区访问Google服务可能会受到限制,我们提供了一个代理服务器解决方案:
-
-### 3.1 部署GA4代理服务器
-1. 在您可以访问的服务器(如阿里云、腾讯云等)上部署`server/ga4-proxy.js`文件
-2. 安装依赖:`npm install`
-3. 启动服务:`npm start`
-
-### 3.2 配置FeHelper使用代理服务器
-在`apps/background/statistics.js`文件中,更新代理服务器URL:
-
-```javascript
-// 国内备用URL (替换为您的代理服务器地址)
-const backupURL = `https://your-ga4-proxy.com/mp/collect?measurement_id=${GA4_MEASUREMENT_ID}&api_secret=${GA4_API_SECRET}`;
-```
-
-请将`your-ga4-proxy.com`替换为您的代理服务器实际域名或IP地址。
-
-## 4. 跟踪的事件类型
-
-本实现会跟踪以下类型的事件:
-
-| 事件名称 | 说明 | 参数 |
-|---------|------|------|
-| extension_loaded | 扩展启动 | extension_version |
-| daily_active_user | 每日活跃用户 | date |
-| extension_installed | 扩展安装 | - |
-| extension_updated | 扩展更新 | previous_version |
-| tool_used | 工具使用 | tool_name, date |
-| usage_summary | 使用摘要 | top_tools |
-
-## 5. 在GA4中查看数据
-
-1. 登录GA4控制台
-2. 浏览`实时`报告查看当前活跃用户
-3. 在`报告`>`参与度`>`事件`中查看各类事件数据
-4. 您可以创建自定义报告和探索查看特定数据
-
-## 6. 隐私保护
-
-为了保护用户隐私,我们收集的数据都是匿名的,不包含任何可以直接识别用户个人身份的信息。在隐私政策中,我们明确说明了所收集数据的类型和用途。
-
-## 7. 注意事项
-
-- GA4有免费使用额度限制,但对于一般规模的浏览器扩展通常足够
-- 如果您的扩展用户数量非常大,可能需要考虑升级到付费版本
-- 请确保在隐私政策中说明您收集的数据类型和用途
-- 代理服务器需要定期维护,确保其正常运行 

+ 0 - 74
server/ga4-proxy.js

@@ -1,74 +0,0 @@
-/**
- * FeHelper GA4代理服务器
- * 用于转发统计数据到GA4服务器,解决国内访问GA服务器被拦截问题
- */
-
-const express = require('express');
-const fetch = require('node-fetch');
-const bodyParser = require('body-parser');
-const app = express();
-const PORT = process.env.PORT || 3001;
-
-// 中间件
-app.use(bodyParser.json());
-app.use((req, res, next) => {
-    res.header('Access-Control-Allow-Origin', '*');
-    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
-    if (req.method === 'OPTIONS') {
-        res.header('Access-Control-Allow-Methods', 'GET, POST');
-        return res.status(200).json({});
-    }
-    next();
-});
-
-// 代理GA4的收集端点
-app.post('/mp/collect', async (req, res) => {
-    try {
-        const measurementId = req.query.measurement_id;
-        const apiSecret = req.query.api_secret;
-        
-        if (!measurementId || !apiSecret) {
-            return res.status(400).json({ error: '缺少必要参数' });
-        }
-        
-        const gaUrl = `https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`;
-        
-        // 记录请求数据但不保存敏感信息
-        console.log(`[${new Date().toISOString()}] 代理请求到GA4: ${measurementId}`);
-        
-        // 转发请求到GA4
-        const gaResponse = await fetch(gaUrl, {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            },
-            body: JSON.stringify(req.body)
-        });
-        
-        // 如果GA4返回成功,返回成功响应
-        if (gaResponse.ok) {
-            return res.status(200).json({ success: true });
-        } else {
-            // 如果GA4返回错误,返回错误响应
-            const errorData = await gaResponse.text();
-            return res.status(gaResponse.status).json({ 
-                error: '转发到GA4失败', 
-                status: gaResponse.status,
-                details: errorData
-            });
-        }
-    } catch (error) {
-        console.error('代理请求失败:', error);
-        res.status(500).json({ error: '代理服务器内部错误' });
-    }
-});
-
-// 健康检查端点
-app.get('/health', (req, res) => {
-    res.status(200).json({ status: 'ok' });
-});
-
-// 启动服务器
-app.listen(PORT, () => {
-    console.log(`FeHelper GA4代理服务器运行在 http://localhost:${PORT}`);
-}); 

+ 0 - 22
server/package.json

@@ -1,22 +0,0 @@
-{
-  "name": "fehelper-statistics-server",
-  "version": "1.0.0",
-  "description": "FeHelper浏览器扩展的统计数据服务器",
-  "main": "ga4-proxy.js",
-  "scripts": {
-    "start": "node ga4-proxy.js",
-    "dev": "nodemon ga4-proxy.js"
-  },
-  "dependencies": {
-    "express": "^4.18.2",
-    "body-parser": "^1.20.2",
-    "node-fetch": "^2.6.9"
-  },
-  "devDependencies": {
-    "nodemon": "^2.0.22"
-  },
-  "engines": {
-    "node": ">=14.0.0"
-  },
-  "private": true
-}