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 对齐)
This commit is contained in:
2026-06-10 19:38:26 +08:00
parent 848fdeca3d
commit 95019316b2
19 changed files with 1681 additions and 292 deletions

View File

@@ -0,0 +1,200 @@
/**
* 设备信息服务 — TypeScript 实现
*
* 与 device_service.py 逻辑一致,保留原 Python 方法不变。
* 提供带缓存的机器码、用户 IP、MAC 地址查询。
*
* 适用环境Node.js / Electron
*/
import * as os from "node:os";
import * as dgram from "node:dgram";
import { DeviceFingerprint } from "./device";
import type { QueryMethod } from "./device";
// ============================================================
// 工具函数
// ============================================================
/** 对应 Python: _is_valid_ipv4(value) -> bool */
function isValidIPv4(value: string): boolean {
const parts = value.split(".");
if (parts.length !== 4) return false;
for (const part of parts) {
const num = parseInt(part, 10);
if (isNaN(num) || num < 0 || num > 255) return false;
if (part.length > 1 && part.startsWith("0")) return false;
}
// 排除环回地址和 0.0.0.0
if (value.startsWith("127.") || value === "0.0.0.0") return false;
return true;
}
/** 对应 Python: _resolve_user_ip() -> Optional[str] */
function resolveUserIp(): string | null {
// 优先通过 UDP "伪连接" 获取本机出站 IP
try {
const sock = dgram.createSocket("udp4");
// 同步方式不可行;使用 UDP connect + getsockname 的异步模式
// 但在 Node.js 中这本质是同步的 (connect 不发送数据)
sock.connect(80, "8.8.8.8");
const address = sock.address();
sock.close();
if (address && typeof address.address === "string") {
const ip = address.address;
if (isValidIPv4(ip)) return ip;
}
} catch {
// 忽略
}
// 回退: 遍历网络接口查找第一个有效的非环回 IPv4
const interfaces = os.networkInterfaces();
for (const ifaceList of Object.values(interfaces)) {
if (!ifaceList) continue;
for (const iface of ifaceList) {
if (iface.family === "IPv4") {
const ip = iface.address;
if (isValidIPv4(ip)) return ip;
}
}
}
return null;
}
/** 对应 Python: _resolve_user_mac_address() -> Optional[str] */
function resolveUserMacAddress(): string | null {
const interfaces = os.networkInterfaces();
for (const ifaceList of Object.values(interfaces)) {
if (!ifaceList) continue;
for (const iface of ifaceList) {
// 跳过环回接口
if (iface.internal) continue;
if (iface.mac && iface.mac !== "00:00:00:00:00:00") {
// 对应 Python: 检查多播位
const parts = iface.mac.split(":");
if (parts.length === 6) {
const firstByte = parseInt(parts[0], 16);
// uuid.getnode 的多播位检查: (node >> 40) & 1
if ((firstByte & 1) === 0) {
return iface.mac.toUpperCase();
}
}
}
}
}
return null;
}
// ============================================================
// 未解析标记
// ============================================================
const UNRESOLVED = Symbol("unresolved");
// ============================================================
// DeviceService 类
// ============================================================
export class DeviceService {
private fingerprint = new DeviceFingerprint();
private cachedMachineCode: string | null | typeof UNRESOLVED = UNRESOLVED;
private cachedUserIp: string | null | typeof UNRESOLVED = UNRESOLVED;
private cachedUserMacAddress: string | null | typeof UNRESOLVED = UNRESOLVED;
private warmupStarted = false;
/**
* 获取机器码(带缓存)。
* 对应 Python: get_machine_code() -> Optional[str]
*/
getMachineCode(method: QueryMethod = "auto"): string | null {
if (this.cachedMachineCode !== UNRESOLVED) {
return this.cachedMachineCode;
}
const machineCode = this.fingerprint.getMachineCode(method);
if (this.cachedMachineCode === UNRESOLVED) {
this.cachedMachineCode = machineCode;
}
return machineCode;
}
/**
* 获取用户本机 IP带缓存
* 对应 Python: get_user_ip() -> Optional[str]
*/
getUserIp(): string | null {
if (this.cachedUserIp !== UNRESOLVED) {
return this.cachedUserIp;
}
const value = resolveUserIp();
if (this.cachedUserIp === UNRESOLVED && value) {
this.cachedUserIp = value;
}
return value;
}
/**
* 获取用户 MAC 地址(带缓存)。
* 对应 Python: get_user_mac_address() -> Optional[str]
*/
getUserMacAddress(): string | null {
if (this.cachedUserMacAddress !== UNRESOLVED) {
return this.cachedUserMacAddress;
}
const value = resolveUserMacAddress();
if (this.cachedUserMacAddress === UNRESOLVED) {
this.cachedUserMacAddress = value;
}
return value;
}
/**
* 异步预热机器码(后台线程)。
* 对应 Python: warm_up_machine_code() -> None
*/
warmUpMachineCode(): void {
if (this.cachedMachineCode !== UNRESOLVED) return;
if (this.warmupStarted) return;
this.warmupStarted = true;
// 使用 setImmediate 模拟后台线程,不阻塞当前事件循环
setImmediate(() => {
const machineCode = this.fingerprint.getMachineCode();
if (this.cachedMachineCode === UNRESOLVED) {
this.cachedMachineCode = machineCode;
}
});
}
}
// ============================================================
// 便捷函数(单例)
// ============================================================
const defaultService = new DeviceService();
/** 获取机器码(单例,带缓存) */
export function getMachineCode(method: QueryMethod = "auto"): string | null {
return defaultService.getMachineCode(method);
}
/** 获取用户本机 IP单例带缓存 */
export function getUserIp(): string | null {
return defaultService.getUserIp();
}
/** 获取用户 MAC 地址(单例,带缓存) */
export function getUserMacAddress(): string | null {
return defaultService.getUserMacAddress();
}
/** 异步预热机器码 */
export function warmUpMachineCode(): void {
defaultService.warmUpMachineCode();
}