文件
OmniCard/mpuniapp/pages/owner/nfc/useSecurity.uts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

391 行
12 KiB
Plaintext

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* OmniCard AI (CRMmp) - 名片主移动端 NFC 安全与远程挂失业务驱动层
* 管理 NFC 资产设备列表、在线/挂失冻结状态流转、防伪 UID 格式校验与安全哈希查验
* 遵循 AGENTS.md 规范与 uts 强类型约束
*/
import { NFCRecord } from '../../../common/types.uts';
import { freezeNfcDevice, unfreezeNfcDevice } from '../../../common/api.uts';
import { getCurrentLocale, getOmniText } from '../../../common/locale.uts';
export type SecurityDeviceStatus = 'active' | 'frozen';
export interface NfcSecurityDevice {
id: number;
uid: string;
cardId?: number | null;
status: SecurityDeviceStatus;
securityHash?: string;
batchNo?: string;
material?: string;
lastScannedAt?: string | null;
}
export interface SecurityState {
devices: NfcSecurityDevice[];
selectedId: number | null;
submitting: boolean;
errorMessage: string;
}
export interface SecurityApi {
freeze: (params: { id: number; uid?: string }) => Promise<unknown>;
unfreeze: (params: { id: number; uid?: string }) => Promise<unknown>;
}
export type SecurityCopyKey =
| 'title'
| 'desc'
| 'statusActive'
| 'statusFrozen'
| 'freezeBtn'
| 'unfreezeBtn'
| 'warningTitle'
| 'warningDesc'
| 'deviceEmpty'
| 'uidLabel'
| 'batchLabel'
| 'antiCounterfeitValid'
| 'antiCounterfeitInvalid'
| 'errorNotFound'
| 'errorAlreadyFrozen'
| 'errorNotFrozen'
| 'freezeSuccess'
| 'unfreezeSuccess'
| 'submitting'
| 'operationError'
| 'shieldTitleActive'
| 'shieldDescActive'
| 'shieldTitleFrozen'
| 'shieldDescFrozen'
| 'chipDetailsTitle'
| 'securityHashLabel'
| 'materialLabel'
| 'protocolLabel'
| 'policyPoint1'
| 'policyPoint2'
| 'policyPoint3'
| 'confirmFreezeTitle'
| 'confirmFreezeDesc'
| 'confirmUnfreezeTitle'
| 'confirmUnfreezeDesc'
| 'confirmBtn'
| 'cancelBtn'
| 'cardListTitle';
const DEFAULT_SALT = 'OMNICARD_SECURE_SALT_2026';
/**
* 规范化 UID:去除冒号、空格、连字符并转大写
*/
export function normalizeUid(raw: string | null | undefined): string {
if (raw == null) {
return '';
}
return String(raw).replace(/[\s:-]/g, '').toUpperCase();
}
/**
* 防伪 UID 格式校验:
* 严格校验十六进制字符,仅放行标准 4 字节(8 字符)或 7 字节(14 字符)NFC 芯片 UID
*/
export function validateNfcUid(raw: string | null | undefined): {
valid: boolean;
normalizedUid: string;
error?: string;
} {
const normalized = normalizeUid(raw);
if (!normalized) {
return {
valid: false,
normalizedUid: '',
error: 'UID 不能为空',
};
}
// 严格检查是否为纯十六进制字符
if (!/^[0-9A-F]+$/.test(normalized)) {
return {
valid: false,
normalizedUid: normalized,
error: 'UID 必须为有效十六进制格式',
};
}
// 校验长度:标准 4 字节 (8 字符) 或 7 字节 (14 字符)
if (normalized.length !== 8 && normalized.length !== 14) {
return {
valid: false,
normalizedUid: normalized,
error: 'UID 长度不符合标准 NFC 规范 (需为 8 或 14 字符)',
};
}
return {
valid: true,
normalizedUid: normalized,
};
}
/**
* 计算确定性防伪安全哈希码
* 基于 UID 与安全盐生成 32 位 Hex 防伪数字签名
*/
export function computeSecurityHash(
uid: string,
salt: string = DEFAULT_SALT
): string {
const cleanUid = normalizeUid(uid);
if (!cleanUid) {
return '';
}
const text = cleanUid + ':' + (salt || DEFAULT_SALT);
let h1 = 0x811c9dc5;
let h2 = 0x9e3779b9;
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
h1 = Math.imul(h1 ^ code, 0x01000193);
h2 = Math.imul(h2 ^ code, 0x5bd1e995);
h1 = (h1 << 13) | (h1 >>> 19);
h2 = (h2 << 15) | (h2 >>> 17);
}
const p1 = (h1 >>> 0).toString(16).padStart(8, '0');
const p2 = (h2 >>> 0).toString(16).padStart(8, '0');
const p3 = ((h1 ^ h2) >>> 0).toString(16).padStart(8, '0');
const p4 = ((h1 + h2) >>> 0).toString(16).padStart(8, '0');
return (p1 + p2 + p3 + p4).toUpperCase();
}
/**
* 查验防伪安全哈希是否匹配,防止物理克隆伪造
*/
export function verifySecurityHash(
uid: string,
hash: string,
salt: string = DEFAULT_SALT
): boolean {
if (!uid || !hash) {
return false;
}
const cleanUid = normalizeUid(uid);
if (!cleanUid) {
return false;
}
const expected = computeSecurityHash(cleanUid, salt);
return expected.toUpperCase() === String(hash).trim().toUpperCase();
}
/**
* 初始化状态对象
*/
export function createSecurityState(
initialDevices?: NfcSecurityDevice[]
): SecurityState {
const devices = initialDevices ? initialDevices.map((d) => ({ ...d })) : [];
const selectedId = devices.length > 0 ? devices[0].id : null;
return {
devices,
selectedId,
submitting: false,
errorMessage: '',
};
}
/**
* 选中指定设备
*/
export function selectDevice(state: SecurityState, id: number): void {
state.selectedId = id;
state.errorMessage = '';
}
/**
* 远程挂失冻结设备 (active -> frozen)
*/
export async function freezeDevice(
state: SecurityState,
id: number,
api?: SecurityApi
): Promise<NfcSecurityDevice> {
const device = state.devices.find((d) => d.id === id);
if (!device) {
throw new Error('NFC 设备不存在');
}
if (device.status === 'frozen') {
throw new Error('设备已处于挂失冻结状态');
}
state.submitting = true;
state.errorMessage = '';
try {
if (api != null && typeof api.freeze === 'function') {
await api.freeze({ id, uid: device.uid });
} else {
await freezeNfcDevice({ id, uid: device.uid });
}
device.status = 'frozen';
return device;
} catch (err) {
const message = err instanceof Error ? err.message : '远程挂失失败';
state.errorMessage = message;
throw new Error(message);
} finally {
state.submitting = false;
}
}
/**
* 远程解除挂失冻结恢复 (frozen -> active)
*/
export async function unfreezeDevice(
state: SecurityState,
id: number,
api?: SecurityApi
): Promise<NfcSecurityDevice> {
const device = state.devices.find((d) => d.id === id);
if (!device) {
throw new Error('NFC 设备不存在');
}
if (device.status !== 'frozen') {
throw new Error('设备未处于挂失冻结状态');
}
state.submitting = true;
state.errorMessage = '';
try {
if (api != null && typeof api.unfreeze === 'function') {
await api.unfreeze({ id, uid: device.uid });
} else {
await unfreezeNfcDevice({ id, uid: device.uid });
}
device.status = 'active';
return device;
} catch (err) {
const message = err instanceof Error ? err.message : '解除挂失失败';
state.errorMessage = message;
throw new Error(message);
} finally {
state.submitting = false;
}
}
/**
* 国际化多语言文案字典解析
*/
export function resolveSecurityText(key: SecurityCopyKey, locale?: string): string {
const lang =
locale != null && String(locale).trim() !== ''
? String(locale).trim()
: getCurrentLocale();
const isZh = lang === 'zh-CN';
switch (key) {
case 'title':
return isZh ? 'NFC 安全与挂失' : 'NFC Security & Remote Lock';
case 'desc':
return isZh
? '物理名片丢失或遭遇异常,可立即远程挂失冻结,保护商业资产安全。'
: 'Remotely freeze lost cards instantly to prevent unauthorized access.';
case 'statusActive':
return isZh ? '正常使用中' : 'Active';
case 'statusFrozen':
return isZh ? '已挂失冻结' : 'Frozen';
case 'freezeBtn':
return isZh ? '远程挂失' : 'Freeze Card';
case 'unfreezeBtn':
return isZh ? '解除挂失恢复' : 'Unfreeze Card';
case 'warningTitle':
return isZh ? '安全保护机制' : 'Security Policy';
case 'warningDesc':
return isZh
? '挂失后将立即切断该实体 NFC 芯片的触碰跳转与数据传输。如已找回芯片,可在此安全解冻。'
: 'Freezing immediately halts touch response and data access on this physical card.';
case 'deviceEmpty':
return isZh ? '暂无名下 NFC 硬件设备' : 'No NFC hardware registered';
case 'uidLabel':
return isZh ? '芯片 UID' : 'Chip UID';
case 'batchLabel':
return isZh ? '出厂批次' : 'Batch No';
case 'antiCounterfeitValid':
return isZh ? '防伪验签通过' : 'Authenticity Verified';
case 'antiCounterfeitInvalid':
return isZh ? '防伪校验异常' : 'Verification Failed';
case 'errorNotFound':
return isZh ? 'NFC 设备不存在' : 'Device not found';
case 'errorAlreadyFrozen':
return isZh ? '设备已处于挂失冻结状态' : 'Device already frozen';
case 'errorNotFrozen':
return isZh ? '设备未处于挂失冻结状态' : 'Device is not frozen';
case 'freezeSuccess':
return isZh ? '设备已成功远程挂失冻结' : 'Card frozen successfully';
case 'unfreezeSuccess':
return isZh ? '设备已成功解冻恢复' : 'Card unfrozen successfully';
case 'submitting':
return isZh ? '处理中...' : 'Processing...';
case 'operationError':
return isZh ? '操作异常' : 'Something went wrong';
case 'shieldTitleActive':
return isZh ? '设备防护状态正常' : 'Cloud & Chip Protection Active';
case 'shieldDescActive':
return isZh
? '物理近场触碰正常联通,触碰即可即时唤起您的专属微官网。'
: 'Physical tap-to-share is active and securely linked.';
case 'shieldTitleFrozen':
return isZh ? '物理芯片已远程挂失冻结' : 'Hardware Frozen & Locked';
case 'shieldDescFrozen':
return isZh
? '已切断物理触碰与数据传输,微官网与访客雷达资产依然安全隔离。'
: 'Physical tap-to-share is blocked. Cloud portal data remains safe.';
case 'chipDetailsTitle':
return isZh ? '芯片防伪与技术参数' : 'Chip Authenticity & Technical Specs';
case 'securityHashLabel':
return isZh ? '防伪安全哈希' : 'Security Hash';
case 'materialLabel':
return isZh ? '物理材质' : 'Material';
case 'protocolLabel':
return isZh ? '通信协议' : 'Protocol';
case 'policyPoint1':
return isZh
? '立即阻断近场触碰响应,拾获者无法获取名片信息或盗用身份'
: 'Instantly halts physical tap-to-share to prevent identity theft';
case 'policyPoint2':
return isZh
? '云端微官网、商业资料与雷达轨迹数据不受影响,完全安全留存'
: 'Cloud portal data, assets, and radar traces remain intact';
case 'policyPoint3':
return isZh
? '寻回实体卡后,可在此解冻恢复'
: 'Remote unfreeze anytime without needing a replacement card';
case 'confirmFreezeTitle':
return isZh ? '确认远程挂失此实体卡?' : 'Freeze this physical card?';
case 'confirmFreezeDesc':
return isZh
? '挂失将立即阻断该实体卡的一切近场触碰功能,拾获者将无法读取任何数据。'
: 'Freezing will immediately halt all touch responses for this physical card.';
case 'confirmUnfreezeTitle':
return isZh ? '确认解除挂失恢复?' : 'Unfreeze and restore card?';
case 'confirmUnfreezeDesc':
return isZh
? '解除挂失后,该实体卡将立即恢复触碰唤起微官网与交换名片功能。'
: 'Unfreezing will restore tap-to-share and portal access immediately.';
case 'confirmBtn':
return isZh ? '确认执行' : 'Confirm';
case 'cancelBtn':
return isZh ? '取消' : 'Cancel';
case 'cardListTitle':
return isZh ? 'NFC 设备列表' : 'Registered NFC Cards';
default:
return '';
}
}