Files
koring-launcher/electron/core/device-id.ts
T
dream_pep 8b7c90b730 feat(device,feedback): 新增设备识别码与意见反馈功能
重构意见反馈设置页面,替换原有占位内容,新增FeedbackButton组件
更新DEV.md与README.md文档,补充对应功能的相关说明
2026-09-04 20:16:51 +08:00

120 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 设备唯一标识(组合指纹 + 回退 MachineGuid
//
// 用途:为“设备识别码”提供尽量唯一且稳定的标识(授权/激活/统计)。
// 来源优先级(均为硬件级,重装系统不变):
// 1. board — 主板 UUIDWin32_ComputerSystemProduct.UUID
// 2. disk — 硬盘序列号(Win32_DiskDrive 首个物理盘)
// 3. bios — BIOS 序列号(Win32_BIOS;存在大量 OEM 默认值,故置后)
// 4. machine— 注册表 MachineGuid(系统安装级,作为最终回退)
// 取第一个有效值 → SHA-256 → 前 32 位十六进制 → UUID 样式分段展示。
import { execSync } from 'child_process';
import { createHash } from 'crypto';
export type DeviceIdSource = 'board' | 'disk' | 'bios' | 'machine' | 'none';
export interface DeviceIdentity {
deviceId: string | null;
source: DeviceIdSource;
}
/** OEM/虚拟机常见的占位序列号,视为无效 */
const INVALID_VALUES = [
'to be filled',
'o.e.m',
'default string',
'system serial number',
'not specified',
'not available',
'none',
'n/a',
'unknown',
'unspecified',
'innotek',
'bochs',
'vmware',
];
function isValid(value?: string | null): value is string {
if (!value) return false;
const v = value.trim();
if (!v) return false;
if (/^0+$/.test(v)) return false; // 全 0
if (/^0{8}-0{4}-0{4}-0{4}-0{12}$/i.test(v)) return false; // 00000000-... 全空 UUID
const low = v.toLowerCase();
return !INVALID_VALUES.some((bad) => low.includes(bad));
}
/** 读取四类候选标识(Windows 一条 PowerShell 完成,逐项容错) */
function probeSources(): { board: string; disk: string; bios: string; machine: string } {
const empty = { board: '', disk: '', bios: '', machine: '' };
if (process.platform !== 'win32') return empty;
const ps = [
'-NoProfile -Command',
"$ErrorActionPreference='SilentlyContinue';",
'$b=(Get-CimInstance Win32_ComputerSystemProduct).UUID;',
'$d=(Get-CimInstance Win32_DiskDrive|Select-Object -First 1).SerialNumber;',
'$i=(Get-CimInstance Win32_BIOS).SerialNumber;',
"$g=(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid;",
'[pscustomobject]@{board=[string]$b;disk=[string]$d;bios=[string]$i;machine=[string]$g}|ConvertTo-Json -Compress',
].join(' ');
try {
const output = execSync(`powershell ${ps}`, { encoding: 'utf-8', timeout: 8000, windowsHide: true });
const parsed = JSON.parse(output.trim() || '{}');
return {
board: String(parsed.board ?? ''),
disk: String(parsed.disk ?? ''),
bios: String(parsed.bios ?? ''),
machine: String(parsed.machine ?? ''),
};
} catch {
return empty;
}
}
/** 32 位十六进制 → UUID 样式(8-4-4-4-12,小写) */
function toUuidLike(hex32: string): string {
const h = hex32.toLowerCase();
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
}
function compute(seed: string): string {
const hex = createHash('sha256').update(`koring-device:${seed}`, 'utf-8').digest('hex');
return toUuidLike(hex.slice(0, 32));
}
let cached: DeviceIdentity | null = null;
/** 获取设备唯一标识(进程内缓存,只探测一次) */
export function getDeviceId(): DeviceIdentity {
if (cached) return cached;
const { board, disk, bios, machine } = probeSources();
let seed: string | undefined;
let source: DeviceIdSource = 'none';
if (isValid(board)) {
seed = board;
source = 'board';
} else if (isValid(disk)) {
seed = disk;
source = 'disk';
} else if (isValid(bios)) {
seed = bios;
source = 'bios';
} else if (isValid(machine)) {
seed = machine;
source = 'machine';
}
cached = seed
? { deviceId: compute(seed), source }
: { deviceId: null, source: 'none' };
return cached;
}
/** 调试用:清空缓存(下次调用重新探测) */
export function resetDeviceIdCache(): void {
cached = null;
}