Browse Source

feat(用量统计): 添加对象存储上传记录功能并重构用量统计模型

- 在会议上传服务中添加对象存储用量记录功能
- 重构原有ASR统计模型为综合用量统计模型
- 新增对象存储记录相关数据结构
- 完善统计数据的聚合计算和持久化
weicu
lxm 11 months ago
parent
commit
8305c89fc4
  1. 123
      lib/data/models/meeting_usage_stats.dart
  2. 35
      lib/data/services/meeting/meeting_upload_service.dart

123
lib/data/models/meeting_usage_stats.dart

@ -1,9 +1,11 @@
///会议模块用量统计的模型类
// ============== ASR 状态常量 ==============
const int asrStatusProcessing = 0; // 处理中
const int asrStatusFailed = 1; // 处理失败
const int asrStatusCompleted = 2; // 已完成
// ============== ASR 单次记录 ==============
// 会议音频文件转文字单次数据
class MeetingAsrRecord {
String id; // 本次ASR唯一标识
@ -60,56 +62,155 @@ class MeetingAsrRecord {
}
}
// 会议音频文件转文字单日统计
class MeetingAsrDailyStats {
// ============== 对象存储单次记录 ==============
// 对象存储单次上传记录
class MeetingObjectStorageRecord {
int meetingId; // 会议记录ID
String storageUrl; // 云端存储URL
int storageBytes; // 文件大小(字节)
int transferBytes; // 实际传输字节数
int uploadTimestamp; // 上传完成时间戳(毫秒)
MeetingObjectStorageRecord({
required this.meetingId,
required this.storageUrl,
required this.storageBytes,
required this.transferBytes,
required this.uploadTimestamp,
});
Map<String, dynamic> toJson() => {
'meetingId': meetingId,
'storageUrl': storageUrl,
'storageBytes': storageBytes,
'transferBytes': transferBytes,
'uploadTimestamp': uploadTimestamp,
};
factory MeetingObjectStorageRecord.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 MeetingObjectStorageRecord(
meetingId: _asInt(json['meetingId']),
storageUrl: (json['storageUrl'] ?? '').toString(),
storageBytes: _asInt(json['storageBytes']),
transferBytes: _asInt(json['transferBytes']),
uploadTimestamp: _asInt(json['uploadTimestamp']),
);
}
}
// ============== 单日统计 ==============
// 会议模块用量单日统计
class MeetingUsageDailyStats {
String date; // 日期字符串: YYYY-MM-DD
String userId; // 用户ID
List<MeetingAsrRecord> items; // 当天所有单次ASR
List<MeetingObjectStorageRecord> storageItems; // 当天所有单次对象存储上传记录
MeetingAsrDailyStats({
MeetingUsageDailyStats({
required this.date,
required this.userId,
required this.items,
required this.storageItems,
});
// 计算型聚合,不冗余存储
// asr聚合计算
int get asrCalls => items.length;
int get audioSecondsTotal => items.fold(0, (s, r) => s + r.audioSeconds);
int get audioBytesTotal => items.fold(0, (s, r) => s + r.fileSizeBytes);
int get transcriptBytesTotal =>
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() => {
'date': date,
'userId': userId,
'items': items.map((e) => e.toJson()).toList(),
'storageItems': storageItems.map((e) => e.toJson()).toList(),
};
factory MeetingAsrDailyStats.fromJson(Map<String, dynamic> json) {
final list = (json['items'] as List? ?? [])
factory MeetingUsageDailyStats.fromJson(Map<String, dynamic> json) {
final asrList = (json['items'] as List? ?? [])
.map((e) => MeetingAsrRecord.fromJson(Map<String, dynamic>.from(e)))
.toList();
return MeetingAsrDailyStats(
final storageList = (json['storageItems'] as List? ?? [])
.map((e) =>
MeetingObjectStorageRecord.fromJson(Map<String, dynamic>.from(e)))
.toList();
return MeetingUsageDailyStats(
date: json['date'] ?? '',
userId: json['userId'] ?? 'anonymous',
items: list,
items: asrList,
storageItems: storageList,
);
}
}
// 会议音频文件转文字历史统计(所有的用量)
class MeetingAsrHistoryStats {
// ============== 历史统计 ==============
// 会议模块用量历史统计(所有的用量)
class MeetingUsageHistoryStats {
String userId; // 用户ID
int allAsrCalls; // 所有ASR调用次数
int allAudioSeconds; // 所有音频总时长(秒)
int allAudioFileBytes; // 所有音频文件大小(字节)
int allTranscriptBytes; // 所有转写后的字节数(utf8)
int allStorageUploads; // 所有对象存储上传次数
int allStorageBytes; // 所有存储文件总大小(字节)
int allTransferBytes; // 所有传输总字节数
MeetingAsrHistoryStats({
MeetingUsageHistoryStats({
required this.userId,
required this.allAsrCalls,
required this.allAudioSeconds,
required this.allAudioFileBytes,
required this.allTranscriptBytes,
required this.allStorageUploads,
required this.allStorageBytes,
required this.allTransferBytes,
});
Map<String, dynamic> toJson() => {
'userId': userId,
'allAsrCalls': allAsrCalls,
'allAudioSeconds': allAudioSeconds,
'allAudioFileBytes': allAudioFileBytes,
'allTranscriptBytes': allTranscriptBytes,
'allStorageUploads': allStorageUploads,
'allStorageBytes': allStorageBytes,
'allTransferBytes': allTransferBytes,
};
factory MeetingUsageHistoryStats.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 MeetingUsageHistoryStats(
userId: json['userId'] ?? 'anonymous',
allAsrCalls: _asInt(json['allAsrCalls']),
allAudioSeconds: _asInt(json['allAudioSeconds']),
allAudioFileBytes: _asInt(json['allAudioFileBytes']),
allTranscriptBytes: _asInt(json['allTranscriptBytes']),
allStorageUploads: _asInt(json['allStorageUploads']),
allStorageBytes: _asInt(json['allStorageBytes']),
allTransferBytes: _asInt(json['allTransferBytes']),
);
}
}

35
lib/data/services/meeting/meeting_upload_service.dart

@ -1,12 +1,15 @@
import 'dart:io';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/upload_oss.dart';
import '../usage_stats_service.dart';
import '../db/sqflite_api.dart';
import 'meeting_task_service.dart';
import 'meeting_task_service2.dart';
/// 管理会议音频文件上传服务
class MeetingUploadService extends GetxService {
final _taskService = Get.find<MeetingTaskService>();
@ -17,7 +20,7 @@ class MeetingUploadService extends GetxService {
RxList uploadList = [].obs;
bool _isUpload = false;
int _handled = 0;
int _handled = 0; //队列处理指针,表示当前循环语句处理的任务数量
@override
void onInit() {
@ -45,7 +48,9 @@ class MeetingUploadService extends GetxService {
bool isUpload = true,
}) async {
bool flash = false;
int fileSize = 0;
try {
fileSize = await File(filePath).length(); //获取文件大小
final details = await SqfliteApi.getMeetingDetails(id);
final int seconds = (details['seconds'] ?? 0) as int;
flash = seconds > 0 && seconds <= 7200;
@ -62,6 +67,7 @@ class MeetingUploadService extends GetxService {
'prompt': '',
'outline': '',
'useFlashASR': flash,
'fileSizeBytes': fileSize,
});
_storage.write("meeting_upload_list", uploadList);
if (isUpload) {
@ -79,17 +85,42 @@ class MeetingUploadService extends GetxService {
if (element['audiourl'].isEmpty) {
meetingId.value = element['id'];
try {
int _sent = 0;
int _total = 0;
final audiourl = await UploadOss.upload(
filepath: element['filepath'],
rootDir: element['rootdir'],
onSendProgress: (int count, int total) {
uploadProgress.value = count / total;
// uploadProgress.value = count / total;
_sent = count;
_total = total;
uploadProgress.value = total > 0 ? count / total : 0.0;
},
);
await SqfliteApi.editMeetingTitle(
element['id'],
{'audiourl': audiourl},
);
try {
// 记录对象存储用量
final svc = Get.isRegistered<UsageStatsService>()
? Get.find<UsageStatsService>()
: Get.put(UsageStatsService(), permanent: true);
int fileSizeBytes = 0;
try {
fileSizeBytes = element['fileSizeBytes'] ?? 0;
} catch (_) {}
final transferred = _total > 0 ? _total : fileSizeBytes;
svc.recordMeetingObjectStorage(
meetingId: element['id'],
storageUrl: audiourl,
storageBytes: fileSizeBytes,
transferBytes: transferred,
uploadTimestamp: DateTime.now().millisecondsSinceEpoch,
);
} catch (e) {
Logger.w('MeetingOSrecord', '对象存储用量记录失败: ' + e.toString());
}
if (element['isTask']) {
if (element['useFlashASR'] == true) {
final service2 = Get.find<MeetingTaskService2>();

Loading…
Cancel
Save