- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
313 行
8.5 KiB
Plaintext
313 行
8.5 KiB
Plaintext
/**
|
||
* 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';
|
||
|
||
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): string {
|
||
const isZh = getCurrentLocale() === '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';
|
||
default:
|
||
return '';
|
||
}
|
||
}
|