73 lines
2.8 KiB
JavaScript
73 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
||
// /oapi 签名票据生成器(对齐 yh-platform-net SignUtil.SignRequest / SignCanonical 协议)
|
||
//
|
||
// 协议:signature = Base64(HmacSHA256(METHOD + "\n" + path[?sortedQuery] + "\n" + timestamp, secret))
|
||
// - query 按 k=v 整体字典序排序后拼接参与签名
|
||
// - timestamp 为毫秒级 Unix 时间戳(服务端校验 5 分钟窗口)
|
||
//
|
||
// 用法:
|
||
// node scripts/oapi-sign.js [METHOD] [path?query]
|
||
// node scripts/oapi-sign.js --curl POST /oapi/print/submit # 输出可直接复制的 curl 命令
|
||
//
|
||
// 密钥来源(优先级):环境变量 APP_KEY / APP_SECRET → 文件默认值(开发联调密钥)
|
||
// 生产环境务必通过环境变量注入,勿提交真实密钥。
|
||
const crypto = require('crypto');
|
||
|
||
const APP_KEY = process.env.APP_KEY || '100002';
|
||
const APP_SECRET = process.env.APP_SECRET || '2314a3b158b24a838b2b9b69fd3db118';
|
||
|
||
// 排序 query(对齐 SignCanonical.SortRawQuery:k=v 整体排序,空值保留 = 号)
|
||
function sortQuery(raw) {
|
||
if (!raw) return '';
|
||
return raw.split('&').filter(Boolean).map(p => {
|
||
const i = p.indexOf('=');
|
||
return (i < 0 ? p : p.slice(0, i)) + '=' + (i < 0 ? '' : p.slice(i + 1));
|
||
}).sort().join('&');
|
||
}
|
||
|
||
function sign(method, pathWithQuery, ts) {
|
||
const qi = pathWithQuery.indexOf('?');
|
||
const target = qi >= 0
|
||
? pathWithQuery.slice(0, qi) + '?' + sortQuery(pathWithQuery.slice(qi + 1))
|
||
: pathWithQuery;
|
||
const source = method.toUpperCase() + '\n' + target + '\n' + ts;
|
||
const sig = crypto.createHmac('sha256', APP_SECRET).update(source, 'utf8').digest('base64');
|
||
return { ts, target, signature: sig };
|
||
}
|
||
|
||
const args = process.argv.slice(2);
|
||
let method = 'GET';
|
||
let pathWithQuery = '/oapi/hello';
|
||
let curlMode = false;
|
||
|
||
for (let i = 0; i < args.length; i++) {
|
||
const a = args[i];
|
||
if (a === '--curl') { curlMode = true; continue; }
|
||
if (!/^-/.test(a)) {
|
||
if (method === 'GET' && !/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/i.test(a) && !a.startsWith('/')) {
|
||
// 第一个非选项参数若不像 METHOD 也不像路径,则视为路径
|
||
pathWithQuery = a;
|
||
} else if (a.startsWith('/')) {
|
||
pathWithQuery = a;
|
||
} else {
|
||
method = a;
|
||
}
|
||
}
|
||
}
|
||
|
||
const { ts, target, signature } = sign(method, pathWithQuery, Date.now().toString());
|
||
|
||
if (curlMode) {
|
||
const u = target.replace(/^\//, '');
|
||
console.log(`curl -i -X ${method.toUpperCase()} "http://localhost:PORT/${u}" \\`);
|
||
console.log(` -H "X-App-Key: ${APP_KEY}" \\`);
|
||
console.log(` -H "X-Timestamp: ${ts}" \\`);
|
||
console.log(` -H "X-Signature: ${signature}"`);
|
||
} else {
|
||
console.log(JSON.stringify({ appKey: APP_KEY, method, target, ts, signature, curl: [
|
||
'-H', `X-App-Key: ${APP_KEY}`,
|
||
'-H', `X-Timestamp: ${ts}`,
|
||
'-H', `X-Signature: ${signature}`
|
||
].join(' ') }, null, 2));
|
||
}
|