feat: multi-module iteration

This commit is contained in:
2026-08-04 21:38:49 +08:00
parent 9d96c73246
commit 71f508e02b
1366 changed files with 202004 additions and 0 deletions
@@ -0,0 +1,158 @@
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';
/** 微信消息推送:Token/timestamp/nonce[/Encrypt] 字典序拼接后 SHA1 */
export function wechatMsgSha1(...parts: string[]): string {
return createHash('sha1').update([...parts].sort().join('')).digest('hex');
}
export function verifyWechatUrlSignature(
token: string,
timestamp: string,
nonce: string,
signature: string,
): boolean {
if (!token || !timestamp || !nonce || !signature) return false;
return wechatMsgSha1(token, timestamp, nonce) === signature;
}
export function verifyWechatMsgSignature(
token: string,
timestamp: string,
nonce: string,
encrypt: string,
msgSignature: string,
): boolean {
if (!token || !timestamp || !nonce || !encrypt || !msgSignature) return false;
return wechatMsgSha1(token, timestamp, nonce, encrypt) === msgSignature;
}
function decodeAesKey(encodingAESKey: string): Buffer {
const key = Buffer.from(`${encodingAESKey.trim()}=`, 'base64');
if (key.length !== 32) {
throw new Error(`EncodingAESKey 无效(解码后应为 32 字节,实际 ${key.length}`);
}
return key;
}
/**
* 解密微信安全模式 Encrypt 字段。
* FullStr = random(16) + msg_len(4 BE) + msg + appid
*/
export function decryptWechatEncrypt(
encryptBase64: string,
encodingAESKey: string,
expectedAppId?: string,
): string {
const aesKey = decodeAesKey(encodingAESKey);
const iv = aesKey.subarray(0, 16);
const decipher = createDecipheriv('aes-256-cbc', aesKey, iv);
const decrypted = Buffer.concat([
decipher.update(Buffer.from(encryptBase64, 'base64')),
decipher.final(),
]);
if (decrypted.length < 20) {
throw new Error('解密结果过短');
}
const msgLen = decrypted.readUInt32BE(16);
const msgStart = 20;
const msgEnd = msgStart + msgLen;
if (msgEnd > decrypted.length) {
throw new Error('解密消息长度非法');
}
const msg = decrypted.subarray(msgStart, msgEnd).toString('utf8');
const appId = decrypted.subarray(msgEnd).toString('utf8');
if (expectedAppId && appId && appId !== expectedAppId) {
throw new Error(`appid 不匹配: got=${appId}`);
}
return msg;
}
/** 加密回包(一般回复 success 明文即可,此函数供需要加密回包时使用) */
export function encryptWechatReply(
plain: string,
encodingAESKey: string,
appId: string,
): string {
const aesKey = decodeAesKey(encodingAESKey);
const iv = aesKey.subarray(0, 16);
const random = randomBytes(16);
const msg = Buffer.from(plain, 'utf8');
const msgLen = Buffer.alloc(4);
msgLen.writeUInt32BE(msg.length, 0);
const full = Buffer.concat([random, msgLen, msg, Buffer.from(appId, 'utf8')]);
const cipher = createCipheriv('aes-256-cbc', aesKey, iv);
return Buffer.concat([cipher.update(full), cipher.final()]).toString('base64');
}
/** 简易 XML 标签提取(微信推送字段无嵌套结构) */
export function parseSimpleXml(xml: string): Record<string, string> {
const out: Record<string, string> = {};
const re = /<([A-Za-z0-9_]+)>(?:<!\[CDATA\[([\s\S]*?)\]\]>|([^<]*))<\/\1>/g;
let m: RegExpExecArray | null;
while ((m = re.exec(xml))) {
out[m[1]] = (m[2] ?? m[3] ?? '').trim();
}
return out;
}
export function parseWechatPushBody(raw: string): Record<string, unknown> {
const trimmed = raw.trim();
if (!trimmed) return {};
if (trimmed.startsWith('{')) {
return JSON.parse(trimmed) as Record<string, unknown>;
}
return parseSimpleXml(trimmed);
}
export type WechatTradeManageEvent = {
event: string;
toUserName?: string;
fromUserName?: string;
createTime?: number;
transactionId?: string;
merchantId?: string;
subMerchantId?: string;
merchantTradeNo?: string;
payTime?: number;
shippedTime?: number;
estimatedSettlementTime?: number;
/** 1 手动确认;2 自动确认(结算推送才有) */
confirmReceiveMethod?: number;
confirmReceiveTime?: number;
settlementTime?: number;
msg?: string;
raw: Record<string, unknown>;
};
function num(v: unknown): number | undefined {
if (v == null || v === '') return undefined;
const n = typeof v === 'number' ? v : Number(v);
return Number.isFinite(n) ? n : undefined;
}
function str(v: unknown): string | undefined {
if (v == null) return undefined;
const s = String(v).trim();
return s || undefined;
}
export function normalizeTradeManageEvent(body: Record<string, unknown>): WechatTradeManageEvent {
return {
event: str(body.Event ?? body.event) || '',
toUserName: str(body.ToUserName),
fromUserName: str(body.FromUserName),
createTime: num(body.CreateTime),
transactionId: str(body.transaction_id),
merchantId: str(body.merchant_id),
subMerchantId: str(body.sub_merchant_id),
merchantTradeNo: str(body.merchant_trade_no),
payTime: num(body.pay_time),
shippedTime: num(body.shipped_time),
estimatedSettlementTime: num(body.estimated_settlement_time),
confirmReceiveMethod: num(body.confirm_receive_method),
confirmReceiveTime: num(body.confirm_receive_time),
settlementTime: num(body.settlement_time),
msg: str(body.msg),
raw: body,
};
}