mirror of
https://github.com/dream-pep/koring-launcher.git
synced 2026-09-12 05:45:18 +08:00
feat(device,feedback): 新增设备识别码与意见反馈功能
重构意见反馈设置页面,替换原有占位内容,新增FeedbackButton组件 更新DEV.md与README.md文档,补充对应功能的相关说明
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
// 设备唯一标识(组合指纹 + 回退 MachineGuid)
|
||||
//
|
||||
// 用途:为“设备识别码”提供尽量唯一且稳定的标识(授权/激活/统计)。
|
||||
// 来源优先级(均为硬件级,重装系统不变):
|
||||
// 1. board — 主板 UUID(Win32_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;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import electron from 'electron';
|
||||
import { execSync } from 'child_process';
|
||||
import * as os from 'os';
|
||||
import { getDeviceId } from '../core/device-id';
|
||||
|
||||
const { ipcMain, app, shell } = electron;
|
||||
|
||||
@@ -68,6 +69,16 @@ export function registerSystemHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
// 设备唯一标识(组合指纹 + 回退 MachineGuid;进程内缓存,首次调用探测一次)
|
||||
ipcMain.handle('system:deviceId', () => {
|
||||
try {
|
||||
const identity = getDeviceId();
|
||||
return { success: true, data: identity, error: null };
|
||||
} catch (e: unknown) {
|
||||
return { success: false, data: null, error: String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
// 进程内存快照(资源/内存调试面板使用;纯读取,无副作用)
|
||||
ipcMain.handle('system:memory', async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user