feat: 分析服务

This commit is contained in:
digua
2025-12-17 20:43:31 +08:00
parent 8abd196931
commit 1e08294472
6 changed files with 191 additions and 48 deletions

View File

@@ -62,6 +62,8 @@ jobs:
APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8 APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
# 分析服务
MAIN_VITE_APTABASE_APP_KEY: ${{ secrets.APTABASE_APP_KEY }}
run: pnpm build:mac run: pnpm build:mac
- name: Upload macOS artifacts - name: Upload macOS artifacts
@@ -112,6 +114,8 @@ jobs:
- name: Build Electron app for Windows - name: Build Electron app for Windows
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} GH_TOKEN: ${{ secrets.GH_TOKEN }}
# 分析服务
MAIN_VITE_APTABASE_APP_KEY: ${{ secrets.APTABASE_APP_KEY }}
run: pnpm build:win run: pnpm build:win
- name: Upload Windows artifacts - name: Upload Windows artifacts

View File

@@ -1,60 +1,68 @@
import { resolve } from 'path' import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite' import { defineConfig, externalizeDepsPlugin, loadEnv } from 'electron-vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite' import ui from '@nuxt/ui/vite'
export default defineConfig({ export default defineConfig(({ mode }) => {
main: { // 加载环境变量(带 MAIN_VITE_ 前缀)
plugins: [externalizeDepsPlugin()], const env = loadEnv(mode)
build: {
rollupOptions: { return {
input: { main: {
index: resolve(__dirname, 'electron/main/index.ts'), plugins: [externalizeDepsPlugin()],
'worker/dbWorker': resolve(__dirname, 'electron/main/worker/dbWorker.ts'), define: {
}, 'process.env.APTABASE_APP_KEY': JSON.stringify(env.MAIN_VITE_APTABASE_APP_KEY || ''),
}, },
}, build: {
}, rollupOptions: {
preload: { input: {
plugins: [externalizeDepsPlugin()], index: resolve(__dirname, 'electron/main/index.ts'),
build: { 'worker/dbWorker': resolve(__dirname, 'electron/main/worker/dbWorker.ts'),
rollupOptions: {
input: {
index: resolve(__dirname, 'electron/preload/index.ts'),
},
},
},
},
renderer: {
resolve: {
alias: {
'@': resolve('src/'),
'~': resolve('src/'),
},
},
plugins: [
vue(),
ui({
ui: {
colors: {
primary: 'pink', // 使用自定义 pink 作为主色
neutral: 'slate',
}, },
}, },
}), },
], },
root: 'src/', preload: {
build: { plugins: [externalizeDepsPlugin()],
sourcemap: false, build: {
rollupOptions: { rollupOptions: {
input: { input: {
index: resolve(__dirname, 'src/index.html'), index: resolve(__dirname, 'electron/preload/index.ts'),
},
}, },
}, },
}, },
server: { renderer: {
host: '0.0.0.0', resolve: {
port: 3400, alias: {
'@': resolve('src/'),
'~': resolve('src/'),
},
},
plugins: [
vue(),
ui({
ui: {
colors: {
primary: 'pink', // 使用自定义 pink 作为主色
neutral: 'slate',
},
},
}),
],
root: 'src/',
build: {
sourcemap: false,
rollupOptions: {
input: {
index: resolve(__dirname, 'src/index.html'),
},
},
},
server: {
host: '0.0.0.0',
port: 3400,
},
}, },
}, }
}) })

112
electron/main/analytics.ts Normal file
View File

@@ -0,0 +1,112 @@
/**
* 应用分析模块
* 使用 Aptabase 进行匿名使用统计
*/
import { app } from 'electron'
import { initialize, trackEvent } from '@aptabase/electron/main'
import * as fs from 'fs'
import * as path from 'path'
// 分析数据存储路径
function getAnalyticsPath(): string {
return path.join(app.getPath('userData'), 'analytics.json')
}
// 分析数据结构
interface AnalyticsData {
lastReportDate: string | null
}
// 读取分析数据
function loadAnalyticsData(): AnalyticsData {
try {
const filePath = getAnalyticsPath()
if (fs.existsSync(filePath)) {
const data = fs.readFileSync(filePath, 'utf-8')
return JSON.parse(data)
}
} catch (error) {
console.error('[Analytics] 读取分析数据失败:', error)
}
return { lastReportDate: null }
}
// 保存分析数据
function saveAnalyticsData(data: AnalyticsData): void {
try {
const filePath = getAnalyticsPath()
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8')
} catch (error) {
console.error('[Analytics] 保存分析数据失败:', error)
}
}
// 获取今天的日期字符串 (YYYY-MM-DD)
function getTodayString(): string {
const now = new Date()
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
}
/**
* 初始化分析模块
* 必须在 app.whenReady() 之前调用
*/
export function initAnalytics(): void {
const appKey = process.env.APTABASE_APP_KEY
if (!appKey) {
return
}
try {
initialize(appKey)
console.log('[Analytics] Aptabase 初始化成功')
} catch (error) {
console.error('[Analytics] Aptabase 初始化失败:', error)
}
}
/**
* 上报每日活跃事件
*/
export function trackDailyActive(): void {
const appKey = process.env.APTABASE_APP_KEY
if (!appKey) {
return
}
try {
const data = loadAnalyticsData()
const today = getTodayString()
// 检查今天是否已经上报过
if (data.lastReportDate === today) {
return
}
// 上报每日活跃事件
trackEvent('app_daily_active')
data.lastReportDate = today
saveAnalyticsData(data)
} catch (error) {
console.error('[Analytics] 上报每日活跃失败:', error)
}
}
/**
* 事件上报
*/
export function trackAppEvent(eventName: string, properties?: Record<string, string | number>): void {
const appKey = process.env.APTABASE_APP_KEY
if (!appKey) {
return
}
try {
trackEvent(eventName, properties)
} catch (error) {
console.error(`[Analytics] 上报事件 ${eventName} 失败:`, error)
}
}

View File

@@ -4,6 +4,7 @@ import { optimizer, is, platform } from '@electron-toolkit/utils'
import * as fs from 'fs/promises' import * as fs from 'fs/promises'
import { checkUpdate } from './update' import { checkUpdate } from './update'
import mainIpcMain from './ipcMain' import mainIpcMain from './ipcMain'
import { initAnalytics, trackDailyActive } from './analytics'
class MainProcess { class MainProcess {
mainWindow: BrowserWindow | null mainWindow: BrowserWindow | null
@@ -44,6 +45,8 @@ class MainProcess {
// 初始化程序 // 初始化程序
async init() { async init() {
initAnalytics()
// 注册应用协议 // 注册应用协议
app.setAsDefaultProtocolClient('chatlab') app.setAsDefaultProtocolClient('chatlab')
@@ -101,6 +104,9 @@ class MainProcess {
// 设置Windows应用程序用户模型id // 设置Windows应用程序用户模型id
if (process.platform === 'win32') app.setAppUserModelId(app.getName()) if (process.platform === 'win32') app.setAppUserModelId(app.getName())
// 记录日活(用于统计操作系统版本、客户端版本,便于更好的适配客户端)
trackDailyActive()
// 创建主窗口 // 创建主窗口
console.log('[Main] Creating window...') console.log('[Main] Creating window...')
await this.createWindow() await this.createWindow()

View File

@@ -25,6 +25,7 @@
"postinstall": "electron-rebuild" "postinstall": "electron-rebuild"
}, },
"dependencies": { "dependencies": {
"@aptabase/electron": "^0.3.1",
"@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",

12
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@aptabase/electron':
specifier: ^0.3.1
version: 0.3.1(electron@35.7.5)
'@electron-toolkit/preload': '@electron-toolkit/preload':
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.2(electron@35.7.5) version: 3.0.2(electron@35.7.5)
@@ -139,6 +142,11 @@ packages:
'@antfu/utils@9.3.0': '@antfu/utils@9.3.0':
resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==} resolution: {integrity: sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==}
'@aptabase/electron@0.3.1':
resolution: {integrity: sha512-FECaGsjuoQu70F+M6V1evdgLP7yaq/sne9fC60AZZ6B9RsCDlxFBnJzdq3+xevHlUx6AB5o9ygUhQ+ONT9EqiA==}
peerDependencies:
electron: '>= 3.x'
'@babel/code-frame@7.27.1': '@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -3722,6 +3730,10 @@ snapshots:
'@antfu/utils@9.3.0': {} '@antfu/utils@9.3.0': {}
'@aptabase/electron@0.3.1(electron@35.7.5)':
dependencies:
electron: 35.7.5
'@babel/code-frame@7.27.1': '@babel/code-frame@7.27.1':
dependencies: dependencies:
'@babel/helper-validator-identifier': 7.28.5 '@babel/helper-validator-identifier': 7.28.5