feat(round-15): 跨境多币种发票税额/线索自动化阶段跃升/名片私密访问锁/芯片激光标刻排版 (P1/P2功能达成)

这个提交包含在:
wikiboss
2026-09-21 02:15:08 +08:00
父节点 45f5f74805
当前提交 e6d06f762c
共有 8 个文件被更改,包括 928 次插入 和 0 次删除

查看文件

@@ -0,0 +1,138 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import * as crypto from 'crypto';
export interface PrivacyUnlockResult {
success: boolean;
accessToken?: string;
error?: string;
}
export interface PrivacyTokenVerifyResult {
valid: boolean;
cardId?: number;
reason?: string;
}
/**
* 针对私密名片口令进行加盐哈希
* 格式: `${salt}:${sha256(password + salt)}`
*/
export function hashPrivacyPassword(password: string): string {
if (!password) {
throw new Error('口令不能为空');
}
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto
.createHash('sha256')
.update(`${password}:${salt}`)
.digest('hex');
return `${salt}:${hash}`;
}
/**
* 恒定时间校验私密口令
*/
export function verifyPrivacyPassword(
passwordAttempt: string,
storedSaltedHash?: string
): boolean {
if (!passwordAttempt || !storedSaltedHash || !storedSaltedHash.includes(':')) {
return false;
}
const [salt, expectedHash] = storedSaltedHash.split(':');
if (!salt || !expectedHash) return false;
const actualHash = crypto
.createHash('sha256')
.update(`${passwordAttempt}:${salt}`)
.digest('hex');
const bufA = Buffer.from(actualHash, 'hex');
const bufB = Buffer.from(expectedHash, 'hex');
if (bufA.length !== bufB.length) return false;
return crypto.timingSafeEqual(bufA, bufB);
}
/**
* 生成临时私密名片访问 Token (默认有效期 2 小时)
* 格式: `${cardId}.${expireTimestamp}.${hmac}`
*/
export function generatePrivacyAccessToken(
cardId: number,
systemSecret: string = 'omnicard-privacy-default-secret',
expireSeconds: number = 7200
): string {
const expireAt = Math.floor(Date.now() / 1000) + expireSeconds;
const payload = `${cardId}.${expireAt}`;
const sig = crypto
.createHmac('sha256', systemSecret)
.update(payload)
.digest('hex');
return `${payload}.${sig}`;
}
/**
* 校验临时私密访问 Token
*/
export function verifyPrivacyAccessToken(
cardId: number,
token?: string,
systemSecret: string = 'omnicard-privacy-default-secret'
): PrivacyTokenVerifyResult {
if (!token || !token.includes('.')) {
return { valid: false, reason: 'Token 格式无效' };
}
const parts = token.split('.');
if (parts.length !== 3) {
return { valid: false, reason: 'Token 缺少签名段' };
}
const [cidStr, expStr, sig] = parts;
const tokenCardId = Number(cidStr);
const expireAt = Number(expStr);
if (tokenCardId !== cardId) {
return { valid: false, reason: 'Token 与名片标识不匹配 (Card mismatch)' };
}
const nowSec = Math.floor(Date.now() / 1000);
if (nowSec > expireAt) {
return { valid: false, reason: 'Token 已过期 (Token expired)' };
}
const payload = `${cidStr}.${expStr}`;
const expectedSig = crypto
.createHmac('sha256', systemSecret)
.update(payload)
.digest('hex');
const bufA = Buffer.from(sig, 'hex');
const bufB = Buffer.from(expectedSig, 'hex');
if (bufA.length !== bufB.length || !crypto.timingSafeEqual(bufA, bufB)) {
return { valid: false, reason: 'Token 签名无效' };
}
return { valid: true, cardId: tokenCardId };
}
/**
* 移动微官网名片私密保护模式服务
*/
@Provide()
export class CardPrivacyPasswordService extends BaseService {
async unlockCard(
cardId: number,
passwordAttempt: string,
storedSaltedHash?: string
): Promise<PrivacyUnlockResult> {
const isOk = verifyPrivacyPassword(passwordAttempt, storedSaltedHash);
if (!isOk) {
return { success: false, error: '访问口令错误,请重新输入' };
}
const accessToken = generatePrivacyAccessToken(cardId);
return { success: true, accessToken };
}
}