feat(telemetry): 将userId改为userSerialNum字符串格式并完善遥测追踪

Backend (presence-service):
- 将EventLog.userId从BigInt改为String类型,存储userSerialNum(如D25121400005)
- 更新Prisma schema,userId字段改为VarChar(20)并添加索引
- 更新心跳相关命令和事件,统一使用userSerialNum字符串
- 添加数据库迁移文件
- 更新相关单元测试和集成测试

Frontend (mobile-app):
- TelemetryEvent新增toServerJson()方法,格式化为后端API期望的格式
- AccountService登录/恢复时设置TelemetryService的userId
- MultiAccountService切换账号时同步更新TelemetryService的userId
- 退出登录时清除TelemetryService的userId
- AuthProvider初始化时设置userId

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-15 06:55:25 -08:00
parent 3942cb405a
commit cc06638e0e
17 changed files with 111 additions and 35 deletions

View File

@ -192,7 +192,8 @@
"Bash(git commit -m \"$(cat <<''EOF''\nfeat(profile): 添加我的伞下功能 - 展示下级用户树形结构\n\n- 后端新增 GET /referral/user/:accountSequence/direct-referrals API\n- 前端新增伞下树组件,支持懒加载、缓存、展开/收起\n- 使用 CustomPaint 绘制父子节点连接线\n- 超出屏幕宽度时显示省略号,点击弹出底部列表\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")", "Bash(git commit -m \"$(cat <<''EOF''\nfeat(profile): 添加我的伞下功能 - 展示下级用户树形结构\n\n- 后端新增 GET /referral/user/:accountSequence/direct-referrals API\n- 前端新增伞下树组件,支持懒加载、缓存、展开/收起\n- 使用 CustomPaint 绘制父子节点连接线\n- 超出屏幕宽度时显示省略号,点击弹出底部列表\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git tag -a v1.0.0-beta1 -m \"$(cat <<''EOF''\nv1.0.0-beta1: 用户首次测试通过\n\n主要修复:\n- fix(reward): 修复 accountSequence 转 userId 时字母前缀导致的 BigInt 转换失败\n- fix(authorization): 修复下级团队认种数重复减去自己认种数的 BUG\n- fix(frontend): 修正权益金额显示与后端实际配置一致\n\n功能完善:\n- 社区权益激活正常\n- 市团队/省团队/市区域/省区域权益考核显示\n- 我的伞下功能 - 展示下级用户树形结构\n\n此版本可作为回滚基准点\nEOF\n)\")", "Bash(git tag -a v1.0.0-beta1 -m \"$(cat <<''EOF''\nv1.0.0-beta1: 用户首次测试通过\n\n主要修复:\n- fix(reward): 修复 accountSequence 转 userId 时字母前缀导致的 BigInt 转换失败\n- fix(authorization): 修复下级团队认种数重复减去自己认种数的 BUG\n- fix(frontend): 修正权益金额显示与后端实际配置一致\n\n功能完善:\n- 社区权益激活正常\n- 市团队/省团队/市区域/省区域权益考核显示\n- 我的伞下功能 - 展示下级用户树形结构\n\n此版本可作为回滚基准点\nEOF\n)\")",
"Bash(ls -la \"c:\\Users\\dong\\Desktop\\rwadurian\\frontend\\mobile-app\\assets\\images\\splash_frames\"\" 2>/dev/null || dir \"c:UsersdongDesktoprwadurianfrontendmobile-appassetsimagessplash_frames\" 2>nul || echo \"目录不存在 \")", "Bash(ls -la \"c:\\Users\\dong\\Desktop\\rwadurian\\frontend\\mobile-app\\assets\\images\\splash_frames\"\" 2>/dev/null || dir \"c:UsersdongDesktoprwadurianfrontendmobile-appassetsimagessplash_frames\" 2>nul || echo \"目录不存在 \")",
"Bash(ls -la \"c:\\Users\\dong\\Desktop\\rwadurian\\frontend\\mobile-app\\lib\\features\"\" | grep -E \"^d \")" "Bash(ls -la \"c:\\Users\\dong\\Desktop\\rwadurian\\frontend\\mobile-app\\lib\\features\"\" | grep -E \"^d \")",
"Bash(git commit -m \"$(cat <<''EOF''\nfeat(telemetry): 将userId改为userSerialNum字符串格式并完善遥测追踪\n\nBackend (presence-service):\n- 将EventLog.userId从BigInt改为String类型,存储userSerialNum(如D25121400005)\n- 更新Prisma schema,userId字段改为VarChar(20)并添加索引\n- 更新心跳相关命令和事件,统一使用userSerialNum字符串\n- 添加数据库迁移文件\n- 更新相关单元测试和集成测试\n\nFrontend (mobile-app):\n- TelemetryEvent新增toServerJson()方法,格式化为后端API期望的格式\n- AccountService登录/恢复时设置TelemetryService的userId\n- MultiAccountService切换账号时同步更新TelemetryService的userId\n- 退出登录时清除TelemetryService的userId\n- AuthProvider初始化时设置userId\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@ -0,0 +1,9 @@
-- Migration: Change user_id from BIGINT to VARCHAR(20)
-- Reason: user_id stores userSerialNum (e.g., "D25121400005") which is a string, not a number
-- Alter the user_id column type from BIGINT to VARCHAR(20)
ALTER TABLE "analytics_event_log"
ALTER COLUMN "user_id" TYPE VARCHAR(20);
-- Add index for user_id for better query performance
CREATE INDEX IF NOT EXISTS "idx_event_log_user_id" ON "analytics_event_log"("user_id");

View File

@ -14,7 +14,7 @@ datasource db {
// 事件日志表 (append-only) // 事件日志表 (append-only)
model EventLog { model EventLog {
id BigInt @id @default(autoincrement()) id BigInt @id @default(autoincrement())
userId BigInt? @map("user_id") userId String? @map("user_id") @db.VarChar(20) // userSerialNum, e.g. "D25121400005"
installId String @map("install_id") @db.VarChar(64) installId String @map("install_id") @db.VarChar(64)
eventName String @map("event_name") @db.VarChar(64) eventName String @map("event_name") @db.VarChar(64)
eventTime DateTime @map("event_time") @db.Timestamptz() eventTime DateTime @map("event_time") @db.Timestamptz()
@ -24,6 +24,7 @@ model EventLog {
@@index([eventTime], name: "idx_event_log_event_time") @@index([eventTime], name: "idx_event_log_event_time")
@@index([eventName], name: "idx_event_log_event_name") @@index([eventName], name: "idx_event_log_event_name")
@@index([eventName, eventTime], name: "idx_event_log_event_name_time") @@index([eventName, eventTime], name: "idx_event_log_event_name_time")
@@index([userId], name: "idx_event_log_user_id")
@@map("analytics_event_log") @@map("analytics_event_log")
} }

View File

@ -24,7 +24,7 @@ export class PresenceController {
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: '心跳上报' }) @ApiOperation({ summary: '心跳上报' })
async heartbeat( async heartbeat(
@CurrentUser('userId') userId: bigint, @CurrentUser('userId') userId: string, // userSerialNum, e.g. "D25121400005"
@Body() dto: HeartbeatDto, @Body() dto: HeartbeatDto,
) { ) {
return this.commandBus.execute( return this.commandBus.execute(

View File

@ -97,7 +97,7 @@ export class RecordEventsHandler implements ICommandHandler<RecordEventsCommand>
private toEventLog(dto: EventItemDto): EventLog { private toEventLog(dto: EventItemDto): EventLog {
return EventLog.create({ return EventLog.create({
userId: dto.userId ? BigInt(dto.userId) : null, userId: dto.userId ?? null, // userSerialNum string, e.g. "D25121400005"
installId: InstallId.fromString(dto.installId), installId: InstallId.fromString(dto.installId),
eventName: EventName.fromString(dto.eventName), eventName: EventName.fromString(dto.eventName),
eventTime: new Date(dto.clientTs * 1000), eventTime: new Date(dto.clientTs * 1000),

View File

@ -1,6 +1,6 @@
export class RecordHeartbeatCommand { export class RecordHeartbeatCommand {
constructor( constructor(
public readonly userId: bigint, public readonly userId: string, // userSerialNum, e.g. "D25121400005"
public readonly installId: string, public readonly installId: string,
public readonly appVersion: string, public readonly appVersion: string,
public readonly clientTs: number, public readonly clientTs: number,

View File

@ -25,8 +25,8 @@ export class RecordHeartbeatHandler implements ICommandHandler<RecordHeartbeatCo
const { userId, installId, appVersion, clientTs } = command; const { userId, installId, appVersion, clientTs } = command;
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
// 1. 更新Redis在线状态 // 1. 更新Redis在线状态 (userId is userSerialNum string, e.g. "D25121400005")
await this.presenceRedisRepository.updateUserPresence(userId.toString(), now); await this.presenceRedisRepository.updateUserPresence(userId, now);
// 2. 发布领域事件 // 2. 发布领域事件
await this.eventPublisher.publish( await this.eventPublisher.publish(

View File

@ -4,7 +4,7 @@ import { EventProperties } from '../value-objects/event-properties.vo';
export class EventLog { export class EventLog {
private _id: bigint | null; private _id: bigint | null;
private _userId: bigint | null; private _userId: string | null; // userSerialNum, e.g. "D25121400005"
private _installId: InstallId; private _installId: InstallId;
private _eventName: EventName; private _eventName: EventName;
private _eventTime: Date; private _eventTime: Date;
@ -18,7 +18,7 @@ export class EventLog {
return this._id; return this._id;
} }
get userId(): bigint | null { get userId(): string | null {
return this._userId; return this._userId;
} }
@ -44,15 +44,15 @@ export class EventLog {
/** /**
* DAU去重的唯一标识 * DAU去重的唯一标识
* 使 userId使 installId * 使 userId (userSerialNum)使 installId
*/ */
get dauIdentifier(): string { get dauIdentifier(): string {
return this._userId?.toString() ?? this._installId.value; return this._userId ?? this._installId.value;
} }
// 工厂方法 // 工厂方法
static create(props: { static create(props: {
userId?: bigint | null; userId?: string | null; // userSerialNum, e.g. "D25121400005"
installId: InstallId; installId: InstallId;
eventName: EventName; eventName: EventName;
eventTime: Date; eventTime: Date;
@ -72,7 +72,7 @@ export class EventLog {
// 从持久化恢复 // 从持久化恢复
static reconstitute(props: { static reconstitute(props: {
id: bigint; id: bigint;
userId: bigint | null; userId: string | null; // userSerialNum, e.g. "D25121400005"
installId: InstallId; installId: InstallId;
eventName: EventName; eventName: EventName;
eventTime: Date; eventTime: Date;

View File

@ -2,7 +2,7 @@ export class HeartbeatReceivedEvent {
static readonly EVENT_NAME = 'presence.heartbeat.received'; static readonly EVENT_NAME = 'presence.heartbeat.received';
constructor( constructor(
public readonly userId: bigint, public readonly userId: string, // userSerialNum, e.g. "D25121400005"
public readonly installId: string, public readonly installId: string,
public readonly occurredAt: Date, public readonly occurredAt: Date,
) {} ) {}

View File

@ -2,7 +2,7 @@ export class SessionStartedEvent {
static readonly EVENT_NAME = 'analytics.session.started'; static readonly EVENT_NAME = 'analytics.session.started';
constructor( constructor(
public readonly userId: bigint | null, public readonly userId: string | null, // userSerialNum, e.g. "D25121400005"
public readonly installId: string, public readonly installId: string,
public readonly occurredAt: Date, public readonly occurredAt: Date,
public readonly metadata: { public readonly metadata: {

View File

@ -3,6 +3,7 @@ import { RecordHeartbeatHandler } from '../../../../src/application/commands/rec
import { RecordHeartbeatCommand } from '../../../../src/application/commands/record-heartbeat/record-heartbeat.command'; import { RecordHeartbeatCommand } from '../../../../src/application/commands/record-heartbeat/record-heartbeat.command';
import { PresenceRedisRepository } from '../../../../src/infrastructure/redis/presence-redis.repository'; import { PresenceRedisRepository } from '../../../../src/infrastructure/redis/presence-redis.repository';
import { EventPublisherService } from '../../../../src/infrastructure/kafka/event-publisher.service'; import { EventPublisherService } from '../../../../src/infrastructure/kafka/event-publisher.service';
import { MetricsService } from '../../../../src/infrastructure/metrics/metrics.service';
import { HeartbeatReceivedEvent } from '../../../../src/domain/events/heartbeat-received.event'; import { HeartbeatReceivedEvent } from '../../../../src/domain/events/heartbeat-received.event';
describe('RecordHeartbeatHandler', () => { describe('RecordHeartbeatHandler', () => {
@ -22,6 +23,10 @@ describe('RecordHeartbeatHandler', () => {
publish: jest.fn(), publish: jest.fn(),
}; };
const mockMetricsService = {
recordHeartbeat: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [ providers: [
RecordHeartbeatHandler, RecordHeartbeatHandler,
@ -33,6 +38,10 @@ describe('RecordHeartbeatHandler', () => {
provide: EventPublisherService, provide: EventPublisherService,
useValue: mockEventPublisher, useValue: mockEventPublisher,
}, },
{
provide: MetricsService,
useValue: mockMetricsService,
},
], ],
}).compile(); }).compile();
@ -48,7 +57,7 @@ describe('RecordHeartbeatHandler', () => {
describe('execute', () => { describe('execute', () => {
it('should record heartbeat and return success', async () => { it('should record heartbeat and return success', async () => {
const command = new RecordHeartbeatCommand( const command = new RecordHeartbeatCommand(
BigInt(12345), 'D25121400005', // userSerialNum
'install-id-123', 'install-id-123',
'1.0.0', '1.0.0',
Date.now(), Date.now(),
@ -64,8 +73,8 @@ describe('RecordHeartbeatHandler', () => {
expect(typeof result.serverTs).toBe('number'); expect(typeof result.serverTs).toBe('number');
}); });
it('should update Redis presence with correct userId', async () => { it('should update Redis presence with correct userId (userSerialNum)', async () => {
const userId = BigInt(99999); const userId = 'D25121499999'; // userSerialNum
const command = new RecordHeartbeatCommand( const command = new RecordHeartbeatCommand(
userId, userId,
'install-id-456', 'install-id-456',
@ -79,14 +88,14 @@ describe('RecordHeartbeatHandler', () => {
await handler.execute(command); await handler.execute(command);
expect(presenceRedisRepository.updateUserPresence).toHaveBeenCalledWith( expect(presenceRedisRepository.updateUserPresence).toHaveBeenCalledWith(
userId.toString(), userId, // userSerialNum is passed directly as string
expect.any(Number), expect.any(Number),
); );
}); });
it('should publish HeartbeatReceivedEvent', async () => { it('should publish HeartbeatReceivedEvent', async () => {
const command = new RecordHeartbeatCommand( const command = new RecordHeartbeatCommand(
BigInt(12345), 'D25121400005',
'install-id-789', 'install-id-789',
'1.0.0', '1.0.0',
Date.now(), Date.now(),
@ -105,7 +114,7 @@ describe('RecordHeartbeatHandler', () => {
it('should return server timestamp close to current time', async () => { it('should return server timestamp close to current time', async () => {
const command = new RecordHeartbeatCommand( const command = new RecordHeartbeatCommand(
BigInt(12345), 'D25121400005',
'install-id', 'install-id',
'1.0.0', '1.0.0',
Date.now(), Date.now(),
@ -124,7 +133,7 @@ describe('RecordHeartbeatHandler', () => {
it('should throw error when Redis update fails', async () => { it('should throw error when Redis update fails', async () => {
const command = new RecordHeartbeatCommand( const command = new RecordHeartbeatCommand(
BigInt(12345), 'D25121400005',
'install-id', 'install-id',
'1.0.0', '1.0.0',
Date.now(), Date.now(),
@ -139,7 +148,7 @@ describe('RecordHeartbeatHandler', () => {
it('should throw error when event publish fails', async () => { it('should throw error when event publish fails', async () => {
const command = new RecordHeartbeatCommand( const command = new RecordHeartbeatCommand(
BigInt(12345), 'D25121400005',
'install-id', 'install-id',
'1.0.0', '1.0.0',
Date.now(), Date.now(),

View File

@ -29,8 +29,8 @@ describe('EventLog Entity', () => {
expect(eventLog.createdAt).toBeInstanceOf(Date); expect(eventLog.createdAt).toBeInstanceOf(Date);
}); });
it('should create EventLog with userId', () => { it('should create EventLog with userId (userSerialNum)', () => {
const userId = BigInt(12345); const userId = 'D25121400005'; // userSerialNum format
const eventLog = EventLog.create({ const eventLog = EventLog.create({
userId, userId,
installId: createInstallId(), installId: createInstallId(),
@ -90,7 +90,7 @@ describe('EventLog Entity', () => {
describe('reconstitute', () => { describe('reconstitute', () => {
it('should reconstitute EventLog from persistence data', () => { it('should reconstitute EventLog from persistence data', () => {
const id = BigInt(999); const id = BigInt(999);
const userId = BigInt(123); const userId = 'D25121400123'; // userSerialNum format
const installId = createInstallId(); const installId = createInstallId();
const eventName = createEventName(); const eventName = createEventName();
const eventTime = createEventTime(); const eventTime = createEventTime();
@ -132,8 +132,8 @@ describe('EventLog Entity', () => {
}); });
describe('dauIdentifier', () => { describe('dauIdentifier', () => {
it('should return userId as string when userId exists', () => { it('should return userId (userSerialNum) when userId exists', () => {
const userId = BigInt(12345); const userId = 'D25121400005';
const eventLog = EventLog.create({ const eventLog = EventLog.create({
userId, userId,
installId: createInstallId(), installId: createInstallId(),
@ -141,7 +141,7 @@ describe('EventLog Entity', () => {
eventTime: createEventTime(), eventTime: createEventTime(),
}); });
expect(eventLog.dauIdentifier).toBe('12345'); expect(eventLog.dauIdentifier).toBe('D25121400005');
}); });
it('should return installId value when userId is null', () => { it('should return installId value when userId is null', () => {
@ -156,7 +156,7 @@ describe('EventLog Entity', () => {
}); });
it('should prefer userId over installId', () => { it('should prefer userId over installId', () => {
const userId = BigInt(999); const userId = 'D25121400999';
const installId = InstallId.fromString('should-not-use-this'); const installId = InstallId.fromString('should-not-use-this');
const eventLog = EventLog.create({ const eventLog = EventLog.create({
userId, userId,
@ -165,7 +165,7 @@ describe('EventLog Entity', () => {
eventTime: createEventTime(), eventTime: createEventTime(),
}); });
expect(eventLog.dauIdentifier).toBe('999'); expect(eventLog.dauIdentifier).toBe('D25121400999');
}); });
}); });
@ -175,7 +175,7 @@ describe('EventLog Entity', () => {
beforeEach(() => { beforeEach(() => {
eventLog = EventLog.reconstitute({ eventLog = EventLog.reconstitute({
id: BigInt(100), id: BigInt(100),
userId: BigInt(200), userId: 'D25121400200',
installId: createInstallId(), installId: createInstallId(),
eventName: createEventName(), eventName: createEventName(),
eventTime: createEventTime(), eventTime: createEventTime(),
@ -189,7 +189,7 @@ describe('EventLog Entity', () => {
}); });
it('should return correct userId', () => { it('should return correct userId', () => {
expect(eventLog.userId).toBe(BigInt(200)); expect(eventLog.userId).toBe('D25121400200');
}); });
it('should return correct installId', () => { it('should return correct installId', () => {

View File

@ -8,6 +8,7 @@ import '../network/api_client.dart';
import '../storage/secure_storage.dart'; import '../storage/secure_storage.dart';
import '../storage/storage_keys.dart'; import '../storage/storage_keys.dart';
import '../errors/exceptions.dart'; import '../errors/exceptions.dart';
import '../telemetry/telemetry_service.dart';
/// ( deviceName ) /// ( deviceName )
class DeviceHardwareInfo { class DeviceHardwareInfo {
@ -591,6 +592,12 @@ class AccountService {
value: 'true', value: 'true',
); );
// ID使userSerialNumD25121400005
if (TelemetryService().isInitialized) {
TelemetryService().setUserId(response.userSerialNum);
debugPrint('$_tag _saveAccountData() - 设置TelemetryService userId: ${response.userSerialNum}');
}
debugPrint('$_tag _saveAccountData() - 账号数据保存完成'); debugPrint('$_tag _saveAccountData() - 账号数据保存完成');
} }
@ -932,6 +939,12 @@ class AccountService {
value: 'true', value: 'true',
); );
// ID使userSerialNumD25121400005
if (TelemetryService().isInitialized) {
TelemetryService().setUserId(response.userSerialNum);
debugPrint('$_tag _saveRecoverAccountData() - 设置TelemetryService userId: ${response.userSerialNum}');
}
debugPrint('$_tag _saveRecoverAccountData() - 恢复账号数据保存完成'); debugPrint('$_tag _saveRecoverAccountData() - 恢复账号数据保存完成');
} }

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../storage/secure_storage.dart'; import '../storage/secure_storage.dart';
import '../storage/storage_keys.dart'; import '../storage/storage_keys.dart';
import '../telemetry/telemetry_service.dart';
/// ///
class AccountSummary { class AccountSummary {
@ -144,6 +145,12 @@ class MultiAccountService {
// //
await setCurrentAccountId(userSerialNum); await setCurrentAccountId(userSerialNum);
// ID使userSerialNumD25121400005
if (TelemetryService().isInitialized) {
TelemetryService().setUserId(userSerialNum);
debugPrint('$_tag switchToAccount() - 设置TelemetryService userId: $userSerialNum');
}
debugPrint('$_tag switchToAccount() - 切换成功'); debugPrint('$_tag switchToAccount() - 切换成功');
return true; return true;
} }
@ -263,6 +270,12 @@ class MultiAccountService {
await _secureStorage.delete(key: key); await _secureStorage.delete(key: key);
} }
// ID
if (TelemetryService().isInitialized) {
TelemetryService().clearUserId();
debugPrint('$_tag logoutCurrentAccount() - 清除TelemetryService userId');
}
debugPrint('$_tag logoutCurrentAccount() - 退出完成,已清除 ${keysToClear.length} 个状态'); debugPrint('$_tag logoutCurrentAccount() - 退出完成,已清除 ${keysToClear.length} 个状态');
} }

View File

@ -102,6 +102,7 @@ class TelemetryEvent extends Equatable {
); );
} }
/// JSON
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'eventId': eventId, 'eventId': eventId,
@ -117,6 +118,30 @@ class TelemetryEvent extends Equatable {
}; };
} }
/// API
/// presence-service :
/// - eventName: string (required)
/// - userId: string (optional) - userSerialNum, e.g. "D25121400005"
/// - installId: string (required)
/// - clientTs: number (required) - Unix timestamp in seconds
/// - properties: object (optional)
Map<String, dynamic> toServerJson() {
return {
'eventName': name,
'userId': userId,
'installId': installId,
'clientTs': timestamp.millisecondsSinceEpoch ~/ 1000,
'properties': {
...?properties,
'eventId': eventId,
'type': type.name,
'level': level.name,
'sessionId': sessionId,
'deviceContextId': deviceContextId,
},
};
}
TelemetryEvent copyWith({ TelemetryEvent copyWith({
String? eventId, String? eventId,
EventType? type, EventType? type,

View File

@ -64,11 +64,11 @@ class TelemetryUploader {
final events = storage.dequeueEvents(batchSize); final events = storage.dequeueEvents(batchSize);
if (events.isEmpty) return true; if (events.isEmpty) return true;
// API // API (使 toServerJson )
final response = await _dio.post( final response = await _dio.post(
'/api/v1/analytics/events', '/api/v1/analytics/events',
data: { data: {
'events': events.map((e) => e.toJson()).toList(), 'events': events.map((e) => e.toServerJson()).toList(),
}, },
options: Options( options: Options(
headers: getAuthHeaders?.call(), headers: getAuthHeaders?.call(),

View File

@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/storage/secure_storage.dart'; import '../../../../core/storage/secure_storage.dart';
import '../../../../core/storage/storage_keys.dart'; import '../../../../core/storage/storage_keys.dart';
import '../../../../core/di/injection_container.dart'; import '../../../../core/di/injection_container.dart';
import '../../../../core/telemetry/telemetry_service.dart';
enum AuthStatus { enum AuthStatus {
initial, initial,
@ -117,6 +118,10 @@ class AuthNotifier extends StateNotifier<AuthState> {
userSerialNum: userSerialNum, userSerialNum: userSerialNum,
referralCode: referralCode, referralCode: referralCode,
); );
// ID使userSerialNumD25121400005
if (userSerialNum != null && TelemetryService().isInitialized) {
TelemetryService().setUserId(userSerialNum);
}
} else if (isAccountCreated) { } else if (isAccountCreated) {
// //
state = state.copyWith( state = state.copyWith(