mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-04-28 07:42:41 +08:00
feat: 分析服务
This commit is contained in:
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
112
electron/main/analytics.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
12
pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user