Files
ele-HeiXiu/electron/preload/device.ts
YoungestSongMo 95019316b2 feat: 前后端联调 — 任务创建 + 账户信息 + 预估花费 (0.0.18)
- task: device_id 改为必填参数,API 层 Omit 封装 + getDeviceId() 自动注入
- pricing.ts: Python 后端 calculate_cost 完整移植,六种定价模式 + 6 个 Bug 修复
  (浮点冗余/维度匹配不一致/null 守卫/matrix 数据提取/unit_scale 等)
- ModelInputForm: 三层兜底预估消费展示 + React Hooks 顺序修复
- AccountInfo: VIP 等级列表匹配,套餐详情展示(说明/周期/席位/到期)
- 模型列表加密缓存 (safe-storage, stale-while-revalidate)
- VIP API 模块 (激活码/等级/订单/模拟支付)
- device_id 统一使用 UUID v4 持久化 (与 login/register 对齐)
2026-06-10 19:38:26 +08:00

280 lines
9.1 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.
/**
* 设备指纹采集 — TypeScript 实现
*
* 与 device.py 逻辑一致,保留原 Python 方法不变。
* 通过采集 UUID / CPU / HDD 三项硬件 ID拼接后做 SHA256 得到 device_id。
*
* 适用环境:
* - Node.js / ElectronWindows通过 child_process 执行 wmic / powershell 采集硬件 ID
* - 浏览器:仅提供 SHA256 哈希工具函数,硬件采集需由上层注入
*/
import * as crypto from "node:crypto";
import { spawnSync } from "node:child_process";
import { platform } from "node:os";
// ============================================================
// 类型定义
// ============================================================
/** 可采集的硬件组件类型 */
export type Component = "uuid" | "cpu" | "hdd";
/** 查询方式 */
export type QueryMethod = "auto" | "ps" | "powershell" | "wmic";
/** 内部解析后的方法 */
type ResolvedMethod = "auto" | "powershell" | "wmic";
// ============================================================
// 查询配置(对应 Python _COMPONENT_QUERIES
// ============================================================
interface ComponentQuery {
powershell: string;
wmicArgs: string[];
headerTokens: string[];
}
const COMPONENT_QUERIES: Record<Component, ComponentQuery> = {
uuid: {
powershell:
"Get-CimInstance -Class Win32_ComputerSystemProduct | " +
"Select-Object -ExpandProperty UUID",
wmicArgs: ["wmic", "csproduct", "get", "uuid"],
headerTokens: ["uuid"],
},
cpu: {
powershell:
"Get-CimInstance -Class Win32_Processor | " +
"Select-Object -ExpandProperty ProcessorId",
wmicArgs: ["wmic", "cpu", "get", "processorid"],
headerTokens: ["processorid"],
},
hdd: {
powershell:
"Get-CimInstance -Class Win32_DiskDrive | " +
"Select-Object -First 1 -ExpandProperty SerialNumber",
wmicArgs: ["wmic", "diskdrive", "get", "serialnumber"],
headerTokens: ["serialnumber"],
},
};
// ============================================================
// 工具函数
// ============================================================
/** 对应 Python: _normalize_identifier(value) -> str */
function normalizeIdentifier(value: string): string {
if (!value) return "";
return value.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
}
/** 对应 Python: _first_value_from_output(raw_output, header_tokens) -> str */
function firstValueFromOutput(
rawOutput: string,
headerTokens: readonly string[],
): string {
if (!rawOutput) return "";
const headers = new Set(headerTokens.map((t) => t.toLowerCase()));
for (const line of rawOutput.split(/\r?\n/)) {
const candidate = line.trim();
if (!candidate) continue;
if (headers.has(candidate.toLowerCase())) continue;
return candidate;
}
return "";
}
/** 对应 Python: _resolve_method(method) -> _ResolvedMethod */
function resolveMethod(method: QueryMethod): ResolvedMethod {
if (method === "ps") return "powershell";
return method;
}
/** 计算 SHA256 哈希hex 大写),对应 Python hashlib.sha256(...).hexdigest().upper() */
function sha256HexUpper(input: string): string {
return crypto.createHash("sha256").update(input, "utf-8").digest("hex").toUpperCase();
}
// ============================================================
// 环境检测
// ============================================================
function isWindows(): boolean {
return platform() === "win32";
}
function isWmicAvailable(): boolean {
if (!isWindows()) return false;
const result = spawnSync("where", ["wmic"], {
timeout: 3000,
stdio: "ignore",
windowsHide: true,
});
return result.status === 0;
}
function isPowershellAvailable(): boolean {
if (!isWindows()) return false;
const result = spawnSync("where", ["powershell"], {
timeout: 3000,
stdio: "ignore",
windowsHide: true,
});
return result.status === 0;
}
// ============================================================
// 进程执行
// ============================================================
/**
* 执行命令并返回 stdout去除首尾空白
* 对应 Python: _run_process(args, timeout=5.0) -> str
*/
function runProcess(args: string[], timeoutSec: number = 5.0): string {
const command = args[0];
const cmdArgs = args.slice(1);
try {
const result = spawnSync(command, cmdArgs, {
timeout: Math.round(timeoutSec * 1000),
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true, // 对应 Python STARTF_USESHOWWINDOW
});
if (result.status !== 0) return "";
return (result.stdout ?? "").trim();
} catch {
return "";
}
}
/** 对应 Python: _run_powershell(command) -> str */
function runPowershell(command: string): string {
if (!isPowershellAvailable()) return "";
const args = ["powershell", "-NoProfile", "-NonInteractive", "-Command", command];
return runProcess(args);
}
/** 对应 Python: _run_wmic(args) -> str */
function runWmic(args: readonly string[]): string {
if (!isWmicAvailable()) return "";
return runProcess([...args]);
}
// ============================================================
// DeviceFingerprint 类
// ============================================================
export class DeviceFingerprint {
// private wmicAvailableCache: boolean | null = null;
// private powershellAvailableCache: boolean | null = null;
// ---------- 组件 ID 查询 ----------
/**
* 查询单个硬件组件 ID。
* 对应 Python: get_component_id(component, method="auto") -> str
*/
getComponentId(component: Component, method: QueryMethod = "auto"): string {
const query = COMPONENT_QUERIES[component];
if (!query) return "";
const resolvedMethod = resolveMethod(method);
const readViaPowershell = (): string => {
const rawOutput = runPowershell(query.powershell);
return firstValueFromOutput(rawOutput, query.headerTokens);
};
const readViaWmic = (): string => {
const rawOutput = runWmic(query.wmicArgs);
return firstValueFromOutput(rawOutput, query.headerTokens);
};
let rawValue = "";
if (resolvedMethod === "powershell") {
rawValue = readViaPowershell();
} else if (resolvedMethod === "wmic") {
rawValue = readViaWmic();
if (!rawValue) {
rawValue = readViaPowershell();
}
} else {
// auto: wmic 优先,回退 powershell
rawValue = readViaWmic() || readViaPowershell();
}
return normalizeIdentifier(rawValue);
}
/**
* 查询单个硬件组件(别名)。
* 对应 Python: get_component(component_type, method="auto") -> str
*/
getComponent(component: Component, method: QueryMethod = "auto"): string {
return this.getComponentId(component, method);
}
// ---------- 机器指纹 ----------
/**
* 生成设备指纹 SHA256。
* 采集 UUID + HDD + CPU拼接后做 SHA256 哈希。
* 对应 Python: fingerprint_sha256(method="auto") -> str | None
*/
fingerprintSha256(method: QueryMethod = "auto"): string | null {
const resolvedMethod = resolveMethod(method);
const wmicOk = isWmicAvailable();
const psOk = isPowershellAvailable();
if (resolvedMethod === "powershell") {
if (!psOk) return null;
} else {
if (!wmicOk && !psOk) return null;
}
const uuidId = this.getComponentId("uuid", method);
const diskId = this.getComponentId("hdd", method);
const cpuId = this.getComponentId("cpu", method);
if (!uuidId && !diskId && !cpuId) {
return null;
}
const combined = `${uuidId}|${diskId}|${cpuId}`;
const normalized = normalizeIdentifier(combined);
return sha256HexUpper(normalized);
}
/**
* 获取机器码(指纹的别名)。
* 对应 Python: get_machine_code(method="auto") -> str | None
*/
getMachineCode(method: QueryMethod = "auto"): string | null {
return this.fingerprintSha256(method);
}
}
// ============================================================
// 便捷函数(无需实例化)
// ============================================================
const defaultFingerprint = new DeviceFingerprint();
/** 获取机器码(单例便捷调用) */
export function getMachineCode(method: QueryMethod = "auto"): string | null {
return defaultFingerprint.getMachineCode(method);
}
/** 获取设备指纹 SHA256单例便捷调用 */
export function fingerprintSha256(method: QueryMethod = "auto"): string | null {
return defaultFingerprint.fingerprintSha256(method);
}
/** 查询单个硬件组件 ID单例便捷调用 */
export function getComponentId(component: Component, method: QueryMethod = "auto"): string {
return defaultFingerprint.getComponentId(component, method);
}