You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
7.1 KiB
221 lines
7.1 KiB
'use strict';
|
|
|
|
const crypto = require('crypto');
|
|
const superagent = require('superagent');
|
|
|
|
const aesEncryptToBase64 = (plainText, key, iv) => {
|
|
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
|
|
let encrypted = cipher.update(String(plainText), 'utf8', 'base64');
|
|
encrypted += cipher.final('base64');
|
|
return encrypted;
|
|
};
|
|
|
|
const aesDecryptFromBase64 = (cipherTextBase64, key, iv) => {
|
|
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
|
|
let decrypted = decipher.update(String(cipherTextBase64), 'base64', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
return decrypted;
|
|
};
|
|
|
|
const parseJsonSafe = (value, fallback = null) => {
|
|
try {
|
|
if (typeof value === 'string') return JSON.parse(value);
|
|
if (value && typeof value === 'object') return value;
|
|
return fallback;
|
|
} catch (_) {
|
|
return fallback;
|
|
}
|
|
};
|
|
|
|
const buildConfig = (app) => {
|
|
const conf = app?.fs?.config?.aideduct || {};
|
|
const key = String(conf.aesKey || '').trim();
|
|
const iv = String(conf.aesIv || '').trim();
|
|
if (Buffer.byteLength(key) !== 16) {
|
|
throw new Error('aideduct.aesKey 长度必须为 16 字节');
|
|
}
|
|
if (Buffer.byteLength(iv) !== 16) {
|
|
throw new Error('aideduct.aesIv 长度必须为 16 字节');
|
|
}
|
|
return {
|
|
baseUrl: String(conf.baseUrl || '').replace(/\/+$/, ''),
|
|
key,
|
|
iv,
|
|
timeout: Number(conf.timeout || 10000),
|
|
};
|
|
};
|
|
|
|
const createClient = (app) => {
|
|
const conf = buildConfig(app);
|
|
|
|
const postEncrypted = async (path, plainBody) => {
|
|
if (!conf.baseUrl) {
|
|
throw new Error('aideduct.baseUrl 未配置');
|
|
}
|
|
const plainText = JSON.stringify(plainBody || {});
|
|
const encryptedData = aesEncryptToBase64(plainText, conf.key, conf.iv);
|
|
const url = `${conf.baseUrl}/${String(path || '').replace(/^\/+/, '')}`;
|
|
|
|
const res = await superagent
|
|
.post(url)
|
|
.type('form')
|
|
.send({ data: encryptedData })
|
|
.timeout(conf.timeout);
|
|
|
|
const body = parseJsonSafe(res?.body, parseJsonSafe(res?.text, {})) || {};
|
|
if (Number(body.code) !== 200) {
|
|
throw new Error(body.msg || '飞尚接口调用失败');
|
|
}
|
|
|
|
if (typeof body.data !== 'string') {
|
|
return body.data || {};
|
|
}
|
|
const decrypted = aesDecryptFromBase64(body.data, conf.key, conf.iv);
|
|
const result = parseJsonSafe(decrypted, null);
|
|
if (!result || typeof result !== 'object') {
|
|
throw new Error('飞尚接口解密后数据格式错误');
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const requireUserId = (userId) => {
|
|
const normalizedUserId = String(userId || '').trim();
|
|
if (!normalizedUserId) throw new Error('缺少 user_id');
|
|
return normalizedUserId;
|
|
};
|
|
|
|
const requireAmount = (amount) => {
|
|
const normalizedAmount = Number(amount);
|
|
if (!Number.isFinite(normalizedAmount) || normalizedAmount <= 0) {
|
|
throw new Error('amount 必须为大于 0 的数字');
|
|
}
|
|
return normalizedAmount;
|
|
};
|
|
|
|
const requireRequestId = (requestId) => {
|
|
const normalizedRequestId = String(requestId || '').trim();
|
|
if (!normalizedRequestId) throw new Error('缺少 request_id');
|
|
return normalizedRequestId;
|
|
};
|
|
|
|
const getBalance = async ({ userId }) => {
|
|
const normalizedUserId = requireUserId(userId);
|
|
const plainRes = await postEncrypted('/getBalance', {
|
|
user_id: normalizedUserId,
|
|
});
|
|
if (Number(plainRes.code) !== 200) {
|
|
throw new Error(plainRes.msg || '余额查询失败');
|
|
}
|
|
const data = plainRes.data || {};
|
|
return {
|
|
code: Number(plainRes.code),
|
|
msg: String(plainRes.msg || ''),
|
|
data: {
|
|
balance: Number(data.balance || 0),
|
|
frozen_balance: Number(data.frozen_balance || 0),
|
|
total_balance: Number(data.total_balance || 0),
|
|
},
|
|
};
|
|
};
|
|
|
|
const getUserInfo = async ({ userId }) => {
|
|
const normalizedUserId = requireUserId(userId);
|
|
const plainRes = await postEncrypted('/getuserinfo', {
|
|
user_id: normalizedUserId,
|
|
});
|
|
if (Number(plainRes.code) !== 200) {
|
|
throw new Error(plainRes.msg || '用户信息查询失败');
|
|
}
|
|
const data = plainRes.data || {};
|
|
return {
|
|
code: Number(plainRes.code),
|
|
msg: String(plainRes.msg || ''),
|
|
data: {
|
|
user: String(data.user || '').trim(),
|
|
email: String(data.email || '').trim(),
|
|
fullname: String(data.fullname || '').trim(),
|
|
create_time: String(data.create_time || '').trim(),
|
|
},
|
|
};
|
|
};
|
|
|
|
const preDeduct = async ({ userId, amount, requestId }) => {
|
|
const plainRes = await postEncrypted('/preDeduct', {
|
|
user_id: requireUserId(userId),
|
|
amount: requireAmount(amount),
|
|
request_id: requireRequestId(requestId),
|
|
});
|
|
if (Number(plainRes.code) !== 200) {
|
|
throw new Error(plainRes.msg || '预扣费失败');
|
|
}
|
|
return {
|
|
code: Number(plainRes.code),
|
|
msg: String(plainRes.msg || ''),
|
|
data: plainRes.data || {},
|
|
};
|
|
};
|
|
|
|
const confirmDeduct = async ({ userId, amount, requestId }) => {
|
|
const plainRes = await postEncrypted('/confirmDeduct', {
|
|
user_id: requireUserId(userId),
|
|
amount: requireAmount(amount),
|
|
request_id: requireRequestId(requestId),
|
|
});
|
|
if (Number(plainRes.code) !== 200) {
|
|
throw new Error(plainRes.msg || '确认扣费失败');
|
|
}
|
|
return {
|
|
code: Number(plainRes.code),
|
|
msg: String(plainRes.msg || ''),
|
|
data: plainRes.data || {},
|
|
};
|
|
};
|
|
|
|
const rollbackDeduct = async ({ userId, amount, requestId }) => {
|
|
const plainRes = await postEncrypted('/rollbackDeduct', {
|
|
user_id: requireUserId(userId),
|
|
amount: requireAmount(amount),
|
|
request_id: requireRequestId(requestId),
|
|
});
|
|
if (Number(plainRes.code) !== 200) {
|
|
throw new Error(plainRes.msg || '回退扣费失败');
|
|
}
|
|
return {
|
|
code: Number(plainRes.code),
|
|
msg: String(plainRes.msg || ''),
|
|
data: plainRes.data || {},
|
|
};
|
|
};
|
|
|
|
const rechargeBalance = async ({ userId, amount, requestId }) => {
|
|
const plainRes = await postEncrypted('/rechargeBalance', {
|
|
user_id: requireUserId(userId),
|
|
amount: requireAmount(amount),
|
|
request_id: requireRequestId(requestId),
|
|
});
|
|
if (Number(plainRes.code) !== 200) {
|
|
throw new Error(plainRes.msg || '充值失败');
|
|
}
|
|
return {
|
|
code: Number(plainRes.code),
|
|
msg: String(plainRes.msg || ''),
|
|
data: plainRes.data || {},
|
|
};
|
|
};
|
|
|
|
return {
|
|
getBalance,
|
|
getUserInfo,
|
|
preDeduct,
|
|
confirmDeduct,
|
|
rollbackDeduct,
|
|
rechargeBalance,
|
|
aesEncryptToBase64: (plainText) => aesEncryptToBase64(plainText, conf.key, conf.iv),
|
|
aesDecryptFromBase64: (cipherTextBase64) => aesDecryptFromBase64(cipherTextBase64, conf.key, conf.iv),
|
|
};
|
|
};
|
|
|
|
module.exports = async (app) => {
|
|
app.fs = app.fs || {};
|
|
app.fs.aideductService = createClient(app);
|
|
};
|
|
|