199 lines
6.5 KiB
Dart
199 lines
6.5 KiB
Dart
|
|
import 'dart:io';
|
|||
|
|
import 'package:logging/logging.dart';
|
|||
|
|
import '../models/client_status.dart';
|
|||
|
|
import '../utils/path_utils.dart';
|
|||
|
|
|
|||
|
|
/// 系统信息获取服务
|
|||
|
|
class SystemInfoService {
|
|||
|
|
static final SystemInfoService _instance = SystemInfoService._internal();
|
|||
|
|
factory SystemInfoService() => _instance;
|
|||
|
|
SystemInfoService._internal();
|
|||
|
|
|
|||
|
|
final Logger _logger = Logger('SystemInfoService');
|
|||
|
|
|
|||
|
|
/// 获取机器码(使用硬盘序列号)
|
|||
|
|
Future<String> getMachineCode() async {
|
|||
|
|
try {
|
|||
|
|
if (Platform.isWindows) {
|
|||
|
|
// 延迟执行,避免启动时崩溃
|
|||
|
|
await Future.delayed(const Duration(seconds: 3));
|
|||
|
|
|
|||
|
|
// 使用 wmic 获取硬盘序列号
|
|||
|
|
try {
|
|||
|
|
final result = await Process.run(
|
|||
|
|
'wmic',
|
|||
|
|
['diskdrive', 'get', 'serialnumber'],
|
|||
|
|
runInShell: true,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (result.exitCode == 0) {
|
|||
|
|
final output = result.stdout.toString();
|
|||
|
|
final lines = output.split('\n');
|
|||
|
|
|
|||
|
|
// 查找序列号(跳过标题行)
|
|||
|
|
for (final line in lines) {
|
|||
|
|
final trimmed = line.trim();
|
|||
|
|
if (trimmed.isNotEmpty &&
|
|||
|
|
!trimmed.toLowerCase().contains('serialnumber') &&
|
|||
|
|
trimmed != '') {
|
|||
|
|
// 提取序列号(去除空格)
|
|||
|
|
final serial = trimmed.replaceAll(RegExp(r'\s+'), '');
|
|||
|
|
if (serial.isNotEmpty) {
|
|||
|
|
_logger.info('获取到硬盘序列号: $serial');
|
|||
|
|
return serial;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_logger.warning('wmic 命令执行成功但未找到有效序列号');
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.warning('使用 wmic 获取硬盘序列号失败: $e');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 备用方案:使用环境变量生成标识符
|
|||
|
|
final computerName = Platform.environment['COMPUTERNAME'] ?? 'UNKNOWN';
|
|||
|
|
final userName = Platform.environment['USERNAME'] ?? 'UNKNOWN';
|
|||
|
|
final userProfile = Platform.environment['USERPROFILE'] ?? '';
|
|||
|
|
|
|||
|
|
final identifier = '${computerName}_${userName}_${userProfile.hashCode.abs()}';
|
|||
|
|
_logger.info('使用备用标识符作为机器码: $identifier');
|
|||
|
|
return identifier;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 非 Windows 系统
|
|||
|
|
_logger.warning('无法获取机器码,使用临时标识');
|
|||
|
|
return 'UNKNOWN_${DateTime.now().millisecondsSinceEpoch}';
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.severe('获取机器码失败: $e');
|
|||
|
|
// 不抛出异常,返回临时标识
|
|||
|
|
return 'ERROR_${DateTime.now().millisecondsSinceEpoch}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取GPU信息(仅在启动时调用一次)
|
|||
|
|
Future<List<GPUInfo>> getGPUInfo() async {
|
|||
|
|
try {
|
|||
|
|
// 使用 nvidia-smi 获取GPU信息
|
|||
|
|
final result = await Process.run(
|
|||
|
|
'nvidia-smi',
|
|||
|
|
['--query-gpu=index,name,memory.total', '--format=csv,noheader,nounits'],
|
|||
|
|
runInShell: true,
|
|||
|
|
).timeout(
|
|||
|
|
const Duration(seconds: 10),
|
|||
|
|
onTimeout: () {
|
|||
|
|
_logger.warning('nvidia-smi 执行超时');
|
|||
|
|
return ProcessResult(0, -1, '', 'timeout');
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (result.exitCode != 0) {
|
|||
|
|
_logger.warning('nvidia-smi 执行失败: exitCode=${result.exitCode}, stderr=${result.stderr}');
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final output = result.stdout.toString().trim();
|
|||
|
|
if (output.isEmpty) {
|
|||
|
|
_logger.info('nvidia-smi 返回空输出,未检测到GPU');
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final lines = output.split('\n');
|
|||
|
|
final gpus = <GPUInfo>[];
|
|||
|
|
|
|||
|
|
for (final line in lines) {
|
|||
|
|
if (line.trim().isEmpty) continue;
|
|||
|
|
|
|||
|
|
final parts = line.split(', ');
|
|||
|
|
if (parts.length >= 3) {
|
|||
|
|
try {
|
|||
|
|
final index = int.parse(parts[0].trim());
|
|||
|
|
final model = parts[1].trim();
|
|||
|
|
final memoryStr = parts[2].trim();
|
|||
|
|
final memory = double.tryParse(memoryStr) ?? 0;
|
|||
|
|
|
|||
|
|
gpus.add(GPUInfo(
|
|||
|
|
index: index,
|
|||
|
|
brand: 'NVIDIA',
|
|||
|
|
model: model,
|
|||
|
|
memory: memory,
|
|||
|
|
));
|
|||
|
|
|
|||
|
|
_logger.info('检测到GPU: 索引=$index, 型号=$model, 显存=${memory}MB');
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.warning('解析GPU信息失败: $line, 错误: $e');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (gpus.isEmpty) {
|
|||
|
|
_logger.info('未检测到GPU');
|
|||
|
|
} else {
|
|||
|
|
_logger.info('成功获取 ${gpus.length} 个GPU信息');
|
|||
|
|
}
|
|||
|
|
return gpus;
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.warning('获取GPU信息失败: $e');
|
|||
|
|
// 不抛出异常,返回空列表,UI会显示"未检测到GPU"
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 读取版本号(从根目录 bin/version 文件)
|
|||
|
|
Future<String> getVersion() async {
|
|||
|
|
try {
|
|||
|
|
final versionPath = PathUtils.binFile('version');
|
|||
|
|
final file = File(versionPath);
|
|||
|
|
if (await file.exists()) {
|
|||
|
|
final content = await file.readAsString();
|
|||
|
|
return content.trim();
|
|||
|
|
} else {
|
|||
|
|
_logger.warning('版本文件不存在: $versionPath');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.warning('读取版本号失败: $e');
|
|||
|
|
}
|
|||
|
|
return '未知版本';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 读取身份信息(从根目录 bin/auth 文件)
|
|||
|
|
Future<String> getAuth() async {
|
|||
|
|
try {
|
|||
|
|
final authPath = PathUtils.binFile('auth');
|
|||
|
|
final file = File(authPath);
|
|||
|
|
if (await file.exists()) {
|
|||
|
|
final content = await file.readAsString();
|
|||
|
|
return content.trim();
|
|||
|
|
} else {
|
|||
|
|
_logger.warning('身份文件不存在: $authPath');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.warning('读取身份信息失败: $e');
|
|||
|
|
}
|
|||
|
|
return '未配置';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 检查管理员权限(Windows)
|
|||
|
|
Future<bool> checkAdminPermission() async {
|
|||
|
|
if (!Platform.isWindows) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 由于 Process.run 会导致崩溃,这里使用环境变量判断
|
|||
|
|
// 如果存在管理员相关的环境变量,可能具有管理员权限
|
|||
|
|
// 这是一个简化的判断,可能不准确,但不会崩溃
|
|||
|
|
final userProfile = Platform.environment['USERPROFILE'];
|
|||
|
|
if (userProfile != null && userProfile.contains('Administrator')) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
// 默认返回 false,表示可能没有管理员权限
|
|||
|
|
_logger.info('无法准确检测管理员权限(避免 Process.run 崩溃),返回 false');
|
|||
|
|
return false;
|
|||
|
|
} catch (e) {
|
|||
|
|
_logger.warning('检查管理员权限失败: $e');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|