Browse Source

refactor(usage_stats): 拆分会议用量统计模型为独立类

将原有的混合统计模型拆分为独立的ASR统计和对象存储统计类
新增组合类MeetingUsageDailySnapshot用于同时展示两种统计
weicu
lxm 11 months ago
parent
commit
1159fc427a
  1. 148
      lib/data/models/meeting_usage_stats.dart
  2. 51
      lib/data/services/usage_stats_service.dart

148
lib/data/models/meeting_usage_stats.dart

@ -106,80 +106,95 @@ class MeetingObjectStorageRecord {
} }
} }
// ============== 单日统计 ============== // 会议音频文件转文字单日统计
// 会议模块用量单日统计 class MeetingAsrDailyStats {
class MeetingUsageDailyStats {
String date; // 日期字符串: YYYY-MM-DD String date; // 日期字符串: YYYY-MM-DD
String userId; // 用户ID String userId; // 用户ID
List<MeetingAsrRecord> items; // 当天所有单次ASR List<MeetingAsrRecord> items; // 当天所有单次ASR
List<MeetingObjectStorageRecord> storageItems; // 当天所有单次对象存储上传记录
MeetingUsageDailyStats({ MeetingAsrDailyStats({
required this.date, required this.date,
required this.userId, required this.userId,
required this.items, required this.items,
required this.storageItems,
}); });
// asr聚合计算 // 计算型聚合,不冗余存储
int get asrCalls => items.length; int get asrCalls => items.length;
int get audioSecondsTotal => items.fold(0, (s, r) => s + r.audioSeconds); int get audioSecondsTotal => items.fold(0, (s, r) => s + r.audioSeconds);
int get audioBytesTotal => items.fold(0, (s, r) => s + r.fileSizeBytes); int get audioBytesTotal => items.fold(0, (s, r) => s + r.fileSizeBytes);
int get transcriptBytesTotal => int get transcriptBytesTotal =>
items.fold(0, (s, r) => s + r.transcriptBytes); items.fold(0, (s, r) => s + r.transcriptBytes);
// 对象存储聚合计算
int get storageUploads => storageItems.length;
int get storageBytesTotal =>
storageItems.fold(0, (s, r) => s + r.storageBytes);
int get transferBytesTotal =>
storageItems.fold(0, (s, r) => s + r.transferBytes);
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'date': date, 'date': date,
'userId': userId, 'userId': userId,
'items': items.map((e) => e.toJson()).toList(), 'items': items.map((e) => e.toJson()).toList(),
'storageItems': storageItems.map((e) => e.toJson()).toList(),
}; };
factory MeetingUsageDailyStats.fromJson(Map<String, dynamic> json) { factory MeetingAsrDailyStats.fromJson(Map<String, dynamic> json) {
final asrList = (json['items'] as List? ?? []) final list = (json['items'] as List? ?? [])
.map((e) => MeetingAsrRecord.fromJson(Map<String, dynamic>.from(e))) .map((e) => MeetingAsrRecord.fromJson(Map<String, dynamic>.from(e)))
.toList(); .toList();
final storageList = (json['storageItems'] as List? ?? []) return MeetingAsrDailyStats(
date: json['date'] ?? '',
userId: json['userId'] ?? 'anonymous',
items: list,
);
}
}
// ============== 对象存储单日统计 ==============
// 👇 职责单一:仅统计对象存储相关数据
class MeetingStorageDailyStats {
String date; // 日期字符串: YYYY-MM-DD
String userId; // 用户ID
List<MeetingObjectStorageRecord> items; // 当天所有单次对象存储记录
MeetingStorageDailyStats({
required this.date,
required this.userId,
required this.items,
});
// 对象存储 聚合计算
int get storageUploads => items.length;
int get storageBytesTotal => items.fold(0, (s, r) => s + r.storageBytes);
int get transferBytesTotal => items.fold(0, (s, r) => s + r.transferBytes);
Map<String, dynamic> toJson() => {
'date': date,
'userId': userId,
'items': items.map((e) => e.toJson()).toList(),
};
factory MeetingStorageDailyStats.fromJson(Map<String, dynamic> json) {
final storageList = (json['items'] as List? ?? [])
.map((e) => .map((e) =>
MeetingObjectStorageRecord.fromJson(Map<String, dynamic>.from(e))) MeetingObjectStorageRecord.fromJson(Map<String, dynamic>.from(e)))
.toList(); .toList();
return MeetingUsageDailyStats(
return MeetingStorageDailyStats(
date: json['date'] ?? '', date: json['date'] ?? '',
userId: json['userId'] ?? 'anonymous', userId: json['userId'] ?? 'anonymous',
items: asrList, items: storageList,
storageItems: storageList,
); );
} }
} }
// ============== 历史统计 ============== // 会议音频文件转文字历史统计(所有的用量)
// 会议模块用量历史统计(所有的用量) class MeetingAsrHistoryStats {
class MeetingUsageHistoryStats {
String userId; // 用户ID String userId; // 用户ID
int allAsrCalls; // 所有ASR调用次数 int allAsrCalls; // 所有ASR调用次数
int allAudioSeconds; // 所有音频总时长(秒) int allAudioSeconds; // 所有音频总时长(秒)
int allAudioFileBytes; // 所有音频文件大小(字节) int allAudioFileBytes; // 所有音频文件大小(字节)
int allTranscriptBytes; // 所有转写后的字节数(utf8) int allTranscriptBytes; // 所有转写后的字节数(utf8)
int allStorageUploads; // 所有对象存储上传次数
int allStorageBytes; // 所有存储文件总大小(字节)
int allTransferBytes; // 所有传输总字节数
MeetingUsageHistoryStats({ MeetingAsrHistoryStats({
required this.userId, required this.userId,
required this.allAsrCalls, required this.allAsrCalls,
required this.allAudioSeconds, required this.allAudioSeconds,
required this.allAudioFileBytes, required this.allAudioFileBytes,
required this.allTranscriptBytes, required this.allTranscriptBytes,
required this.allStorageUploads,
required this.allStorageBytes,
required this.allTransferBytes,
}); });
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -188,12 +203,9 @@ class MeetingUsageHistoryStats {
'allAudioSeconds': allAudioSeconds, 'allAudioSeconds': allAudioSeconds,
'allAudioFileBytes': allAudioFileBytes, 'allAudioFileBytes': allAudioFileBytes,
'allTranscriptBytes': allTranscriptBytes, 'allTranscriptBytes': allTranscriptBytes,
'allStorageUploads': allStorageUploads,
'allStorageBytes': allStorageBytes,
'allTransferBytes': allTransferBytes,
}; };
factory MeetingUsageHistoryStats.fromJson(Map<String, dynamic> json) { factory MeetingAsrHistoryStats.fromJson(Map<String, dynamic> json) {
int _asInt(dynamic v) { int _asInt(dynamic v) {
if (v == null) return 0; if (v == null) return 0;
if (v is int) return v; if (v is int) return v;
@ -202,15 +214,79 @@ class MeetingUsageHistoryStats {
return 0; return 0;
} }
return MeetingUsageHistoryStats( return MeetingAsrHistoryStats(
userId: json['userId'] ?? 'anonymous', userId: json['userId'] ?? 'anonymous',
allAsrCalls: _asInt(json['allAsrCalls']), allAsrCalls: _asInt(json['allAsrCalls']),
allAudioSeconds: _asInt(json['allAudioSeconds']), allAudioSeconds: _asInt(json['allAudioSeconds']),
allAudioFileBytes: _asInt(json['allAudioFileBytes']), allAudioFileBytes: _asInt(json['allAudioFileBytes']),
allTranscriptBytes: _asInt(json['allTranscriptBytes']), allTranscriptBytes: _asInt(json['allTranscriptBytes']),
);
}
}
// ============== 对象存储历史统计 ==============
// 👇 职责单一:仅统计对象存储累计数据
class MeetingStorageHistoryStats {
String userId; // 用户ID
int allStorageUploads; // 所有对象存储上传次数
int allStorageBytes; // 所有存储文件总大小(字节)
int allTransferBytes; // 所有传输总字节数
MeetingStorageHistoryStats({
required this.userId,
required this.allStorageUploads,
required this.allStorageBytes,
required this.allTransferBytes,
});
Map<String, dynamic> toJson() => {
'userId': userId,
'allStorageUploads': allStorageUploads,
'allStorageBytes': allStorageBytes,
'allTransferBytes': allTransferBytes,
};
factory MeetingStorageHistoryStats.fromJson(Map<String, dynamic> json) {
int _asInt(dynamic v) {
if (v == null) return 0;
if (v is int) return v;
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
return MeetingStorageHistoryStats(
userId: json['userId'] ?? 'anonymous',
allStorageUploads: _asInt(json['allStorageUploads']), allStorageUploads: _asInt(json['allStorageUploads']),
allStorageBytes: _asInt(json['allStorageBytes']), allStorageBytes: _asInt(json['allStorageBytes']),
allTransferBytes: _asInt(json['allTransferBytes']), allTransferBytes: _asInt(json['allTransferBytes']),
); );
} }
} }
// 👇 当需要同时展示两种统计时使用
class MeetingUsageDailySnapshot {
String date;
String userId;
MeetingAsrDailyStats? asrStats;
MeetingStorageDailyStats? storageStats;
MeetingUsageDailySnapshot({
required this.date,
required this.userId,
this.asrStats,
this.storageStats,
});
}
class MeetingUsageHistorySnapshot {
String userId;
MeetingAsrHistoryStats? asrStats;
MeetingStorageHistoryStats? storageStats;
MeetingUsageHistorySnapshot({
required this.userId,
this.asrStats,
this.storageStats,
});
}

51
lib/data/services/usage_stats_service.dart

@ -12,6 +12,7 @@ import '../models/user_Info.dart';
class UsageStatsService extends GetxService { class UsageStatsService extends GetxService {
static const String _dailyStatsKey = 'daily_translation_stats'; static const String _dailyStatsKey = 'daily_translation_stats';
static const String _meetingDailyKey = 'daily_meeting_asr_stats'; static const String _meetingDailyKey = 'daily_meeting_asr_stats';
static const String _meetingOsDailykey = 'daily_meeting_os_stats';
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
@ -92,6 +93,56 @@ class UsageStatsService extends GetxService {
return '$y-$m-$day'; return '$y-$m-$day';
} }
/// 记录单次对象存储上传
/// [meetingId] 会议ID
/// [storageUrl] 云端存储URL
/// [storageBytes] 文件大小(字节)
/// [transferBytes] 数据传输字节数
/// [uploadTimestamp] 上传完成时间戳(毫秒)
void recordMeetingObjectStorage({
required int meetingId,
required String storageUrl,
required int storageBytes,
required int transferBytes,
required int uploadTimestamp,
}) {
final dateKey =
_formatDate(DateTime.fromMillisecondsSinceEpoch(uploadTimestamp));
final storageKey = '${_meetingOsDailykey}_$dateKey';
final raw = _storage.read(storageKey);
List<MeetingObjectStorageRecord> uploads = [];
if (raw is List) {
uploads = raw
.map((e) =>
MeetingObjectStorageRecord.fromJson(Map<String, dynamic>.from(e)))
.toList();
}
// final id = 'os_${uploadTimestamp}_${meetingId}';
final record = MeetingObjectStorageRecord(
meetingId: meetingId,
storageUrl: storageUrl,
storageBytes: storageBytes,
transferBytes: transferBytes,
uploadTimestamp: uploadTimestamp,
);
// 使用 meetingId + storageUrl 作为唯一标识来判断重复
final exists = uploads.indexWhere(
(e) => e.meetingId == meetingId && e.storageUrl == storageUrl) >=
0;
if (!exists) {
uploads.add(record);
_storage.write(storageKey, uploads.map((e) => e.toJson()).toList());
Logger.d('MeetingOSrecord',
'新增对象存储上传: meetingId=$meetingId, size=$storageBytes, transfer=$transferBytes');
} else {
Logger.d('MeetingOSrecord',
'重复对象存储上传忽略: meetingId=$meetingId, url=$storageUrl');
}
}
/// 添加或更新会议ASR记录 /// 添加或更新会议ASR记录
/// [record] 会议ASR记录 /// [record] 会议ASR记录
/// 该方法用于记录一次会议语音识别(ASR)的提交记录。它会根据记录的时间戳确定日期, /// 该方法用于记录一次会议语音识别(ASR)的提交记录。它会根据记录的时间戳确定日期,

Loading…
Cancel
Save