统一前后端代码格式化配置
This commit is contained in:
@@ -36,7 +36,10 @@ try {
|
||||
console.log('\n已清空测试业务数据。')
|
||||
console.log('保留内容:fulfillment profiles/bindings、admin users、配置文件。')
|
||||
} catch (error) {
|
||||
console.error('\n清理失败:', error instanceof Error ? error.message : String(error || '未知错误'))
|
||||
console.error(
|
||||
'\n清理失败:',
|
||||
error instanceof Error ? error.message : String(error || '未知错误'),
|
||||
)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
await closeDb()
|
||||
@@ -64,7 +67,8 @@ function parseArgs(argv) {
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
console.log(
|
||||
`
|
||||
用法:
|
||||
npm run cleanup:dev-data -- [--apply]
|
||||
|
||||
@@ -80,7 +84,8 @@ function printHelp() {
|
||||
admin_users / admin_audit_logs
|
||||
本地配置文件
|
||||
数据库连接: ${String(runtimeConfig.database?.url || '').trim() || '(未配置 DATABASE_URL)'}
|
||||
`.trim())
|
||||
`.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
function printSummary(counts) {
|
||||
|
||||
@@ -20,10 +20,7 @@ const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(CURRENT_DIR, '..')
|
||||
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, '../..')
|
||||
|
||||
loadEnvFiles([
|
||||
path.join(WORKSPACE_ROOT, '.env'),
|
||||
path.join(PROJECT_ROOT, '.env'),
|
||||
])
|
||||
loadEnvFiles([path.join(WORKSPACE_ROOT, '.env'), path.join(PROJECT_ROOT, '.env')])
|
||||
|
||||
const accessToken = String(process.env.KUASHOU_INDUSTRY_ACCESS_TOKEN || 'your_access_token').trim()
|
||||
const appKey = String(process.env.KUASHOU_INDUSTRY_APP_KEY || 'your_app_key').trim()
|
||||
@@ -31,7 +28,7 @@ const signSecret = String(process.env.KUASHOU_INDUSTRY_SIGN_SECRET || 'your_sign
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const mode = String(args.mode || 'send').trim() // send | consume | destroy
|
||||
const oid = String(args.oid || String(Date.now())).trim() // 快手要求纯数字 oid
|
||||
const oid = String(args.oid || String(Date.now())).trim() // 快手要求纯数字 oid
|
||||
const sendNum = normalizeInt(args.num, 3)
|
||||
const token = String(args.token || `test-token-${Date.now()}`).trim()
|
||||
const bodyAccessToken = String(args.accessToken || accessToken).trim()
|
||||
@@ -41,57 +38,60 @@ const KUAISHOU_OPEN_API = 'https://openapi.kwaixiaodian.com'
|
||||
const now = Date.now()
|
||||
const validEnd = now + 30 * 24 * 60 * 60 * 1000
|
||||
|
||||
const bizParams: JsonObject = mode === 'consume'
|
||||
? {
|
||||
oid,
|
||||
etickets: Array.from({ length: sendNum }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
num: 1,
|
||||
goodsValue: 10,
|
||||
})),
|
||||
status: 'CONSUMED',
|
||||
consumeType: 'consume',
|
||||
consumeTime: now,
|
||||
storeName: '测试门店',
|
||||
storeAddress: '测试地址',
|
||||
seriallNum: `SN${Date.now()}`,
|
||||
token,
|
||||
}
|
||||
: mode === 'destroy'
|
||||
? {
|
||||
oid,
|
||||
etickets: Array.from({ length: sendNum }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
num: 1,
|
||||
goodsValue: 100,
|
||||
})),
|
||||
reason: 'ETICKET_EXPIRED',
|
||||
token,
|
||||
}
|
||||
: {
|
||||
oid,
|
||||
sendType: 'VIRTUAL',
|
||||
etickets: Array.from({ length: sendNum }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
num: 1,
|
||||
validStartTime: now,
|
||||
validEndTime: validEnd,
|
||||
})),
|
||||
sendNum,
|
||||
token,
|
||||
}
|
||||
const bizParams: JsonObject =
|
||||
mode === 'consume'
|
||||
? {
|
||||
oid,
|
||||
etickets: Array.from({ length: sendNum }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
num: 1,
|
||||
goodsValue: 10,
|
||||
})),
|
||||
status: 'CONSUMED',
|
||||
consumeType: 'consume',
|
||||
consumeTime: now,
|
||||
storeName: '测试门店',
|
||||
storeAddress: '测试地址',
|
||||
seriallNum: `SN${Date.now()}`,
|
||||
token,
|
||||
}
|
||||
: mode === 'destroy'
|
||||
? {
|
||||
oid,
|
||||
etickets: Array.from({ length: sendNum }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
num: 1,
|
||||
goodsValue: 100,
|
||||
})),
|
||||
reason: 'ETICKET_EXPIRED',
|
||||
token,
|
||||
}
|
||||
: {
|
||||
oid,
|
||||
sendType: 'VIRTUAL',
|
||||
etickets: Array.from({ length: sendNum }, (_, i) => ({
|
||||
id: String(i + 1),
|
||||
num: 1,
|
||||
validStartTime: now,
|
||||
validEndTime: validEnd,
|
||||
})),
|
||||
sendNum,
|
||||
token,
|
||||
}
|
||||
|
||||
const method = mode === 'consume'
|
||||
? 'integration.callback.virtual.eticket.consume'
|
||||
: mode === 'destroy'
|
||||
? 'integration.callback.virtual.eticket.destroy'
|
||||
: 'integration.callback.virtual.eticket.send'
|
||||
const method =
|
||||
mode === 'consume'
|
||||
? 'integration.callback.virtual.eticket.consume'
|
||||
: mode === 'destroy'
|
||||
? 'integration.callback.virtual.eticket.destroy'
|
||||
: 'integration.callback.virtual.eticket.send'
|
||||
|
||||
const endpoint = mode === 'consume'
|
||||
? '/integration/callback/virtual/eticket/consume'
|
||||
: mode === 'destroy'
|
||||
? '/integration/callback/virtual/eticket/destroy'
|
||||
: '/integration/callback/virtual/eticket/send'
|
||||
const endpoint =
|
||||
mode === 'consume'
|
||||
? '/integration/callback/virtual/eticket/consume'
|
||||
: mode === 'destroy'
|
||||
? '/integration/callback/virtual/eticket/destroy'
|
||||
: '/integration/callback/virtual/eticket/send'
|
||||
|
||||
const paramStr = JSON.stringify(bizParams)
|
||||
|
||||
@@ -112,7 +112,9 @@ const modeLabel = mode === 'consume' ? '核销' : mode === 'destroy' ? '销毁'
|
||||
console.log(`# 电子凭证${modeLabel}回调 curl 命令`)
|
||||
console.log(`# oid=${oid} sendNum=${sendNum} access_token=${bodyAccessToken.slice(0, 10)}...`)
|
||||
console.log(`# 先测试网络连通性:`)
|
||||
console.log(`curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://openapi.kwaixiaodian.com/`)
|
||||
console.log(
|
||||
`curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://openapi.kwaixiaodian.com/`,
|
||||
)
|
||||
console.log('# 如果返回 000,说明网络不通,需要关闭代理或换服务器')
|
||||
console.log('')
|
||||
console.log('curl -X POST \\')
|
||||
@@ -130,10 +132,15 @@ console.log('')
|
||||
|
||||
// 本地签名校验(不调快手)
|
||||
console.log('# 本地签名校验(不会真正调用快手):')
|
||||
const localEndpoint = mode === 'consume' ? 'consume-code' : mode === 'destroy' ? 'destroy-code' : 'send-code'
|
||||
console.log(`curl -s -X POST http://127.0.0.1:3000/api/v1/open/kuaishou-industry/${localEndpoint} \\`)
|
||||
const localEndpoint =
|
||||
mode === 'consume' ? 'consume-code' : mode === 'destroy' ? 'destroy-code' : 'send-code'
|
||||
console.log(
|
||||
`curl -s -X POST http://127.0.0.1:3000/api/v1/open/kuaishou-industry/${localEndpoint} \\`,
|
||||
)
|
||||
console.log(" -H 'Content-Type: application/x-www-form-urlencoded' \\")
|
||||
console.log(` -d 'appkey=${appKey}&version=1×tamp=${now}&signMethod=MD5&access_token=&method=&sign=${sign}¶m=${encodeURIComponent(paramStr)}'`)
|
||||
console.log(
|
||||
` -d 'appkey=${appKey}&version=1×tamp=${now}&signMethod=MD5&access_token=&method=&sign=${sign}¶m=${encodeURIComponent(paramStr)}'`,
|
||||
)
|
||||
console.log('')
|
||||
|
||||
function signPayload(params: JsonObject, secret: string): string {
|
||||
@@ -141,7 +148,11 @@ function signPayload(params: JsonObject, secret: string): string {
|
||||
.filter(([k]) => k !== 'sign' && k !== 'signSecret')
|
||||
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
||||
const qs = entries.map(([k, v]) => `${k}=${stringify(v)}`).join('&')
|
||||
return crypto.createHash('md5').update(`${qs}&signSecret=${secret}`, 'utf8').digest('hex').toLowerCase()
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(`${qs}&signSecret=${secret}`, 'utf8')
|
||||
.digest('hex')
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function stringify(v: unknown): string {
|
||||
@@ -164,7 +175,10 @@ function parseArgs(raw: string[]): JsonObject {
|
||||
if (!arg) continue
|
||||
const n = arg.startsWith('--') ? arg.slice(2) : arg
|
||||
const eq = n.indexOf('=')
|
||||
if (eq < 0) { p[n] = true; continue }
|
||||
if (eq < 0) {
|
||||
p[n] = true
|
||||
continue
|
||||
}
|
||||
p[n.slice(0, eq).trim()] = n.slice(eq + 1).trim()
|
||||
}
|
||||
return p
|
||||
|
||||
@@ -19,16 +19,15 @@ const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(CURRENT_DIR, '..')
|
||||
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, '../..')
|
||||
|
||||
loadEnvFiles([
|
||||
path.join(WORKSPACE_ROOT, '.env'),
|
||||
path.join(PROJECT_ROOT, '.env'),
|
||||
])
|
||||
loadEnvFiles([path.join(WORKSPACE_ROOT, '.env'), path.join(PROJECT_ROOT, '.env')])
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
const baseUrl = String(args.baseUrl || 'http://127.0.0.1').replace(/\/+$/, '')
|
||||
const appKey = String(args.appKey || process.env.KUASHOU_INDUSTRY_APP_KEY || '').trim()
|
||||
const signSecret = String(args.signSecret || process.env.KUASHOU_INDUSTRY_SIGN_SECRET || '').trim()
|
||||
const signMethod = String(args.signMethod || 'MD5').trim().toUpperCase()
|
||||
const signMethod = String(args.signMethod || 'MD5')
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
|
||||
if (!appKey || !signSecret) {
|
||||
console.error('请配置 appKey 和 signSecret')
|
||||
@@ -85,7 +84,8 @@ function signKuaishou(params: JsonObject, method: string): string {
|
||||
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
||||
const qs = entries.map(([k, v]) => `${k}=${stringifyVal(v)}`).join('&')
|
||||
const src = `${qs}&signSecret=${signSecret}`
|
||||
if (method === 'HMAC_SHA256') return crypto.createHmac('sha256', signSecret).update(src, 'utf8').digest('base64')
|
||||
if (method === 'HMAC_SHA256')
|
||||
return crypto.createHmac('sha256', signSecret).update(src, 'utf8').digest('base64')
|
||||
return crypto.createHash('md5').update(src, 'utf8').digest('hex').toLowerCase()
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ async function call(endpoint: string, params: JsonObject) {
|
||||
body.append('param', String(params.param || '{}'))
|
||||
|
||||
const started = Date.now()
|
||||
let status = 0; let json: JsonObject = {}
|
||||
let status = 0
|
||||
let json: JsonObject = {}
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
@@ -115,8 +116,14 @@ async function call(endpoint: string, params: JsonObject) {
|
||||
})
|
||||
status = res.status
|
||||
const text = await res.text()
|
||||
try { json = JSON.parse(text) } catch { json = { raw: text } }
|
||||
} catch (err) { json = { error: String(err) } }
|
||||
try {
|
||||
json = JSON.parse(text)
|
||||
} catch {
|
||||
json = { raw: text }
|
||||
}
|
||||
} catch (err) {
|
||||
json = { error: String(err) }
|
||||
}
|
||||
return { status, json, ms: Date.now() - started }
|
||||
}
|
||||
|
||||
@@ -409,7 +416,10 @@ function parseArgs(rawArgs: string[]) {
|
||||
if (!arg) continue
|
||||
const n = arg.startsWith('--') ? arg.slice(2) : arg
|
||||
const eq = n.indexOf('=')
|
||||
if (eq < 0) { parsed[n] = true; continue }
|
||||
if (eq < 0) {
|
||||
parsed[n] = true
|
||||
continue
|
||||
}
|
||||
parsed[n.slice(0, eq).trim()] = n.slice(eq + 1).trim()
|
||||
}
|
||||
return parsed
|
||||
|
||||
@@ -40,7 +40,11 @@ async function main() {
|
||||
|
||||
let appliedNames = new Set<string>()
|
||||
try {
|
||||
const result = await query<{ name: string | null; filename: string | null; run_on: string | null }>(
|
||||
const result = await query<{
|
||||
name: string | null
|
||||
filename: string | null
|
||||
run_on: string | null
|
||||
}>(
|
||||
`
|
||||
SELECT name, filename, run_on
|
||||
FROM schema_migrations
|
||||
@@ -49,9 +53,7 @@ async function main() {
|
||||
)
|
||||
|
||||
appliedNames = new Set(
|
||||
result.rows
|
||||
.map((row) => normalizeMigrationName(row.filename || row.name))
|
||||
.filter(Boolean),
|
||||
result.rows.map((row) => normalizeMigrationName(row.filename || row.name)).filter(Boolean),
|
||||
)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
|
||||
@@ -20,7 +20,9 @@ if (args.help) {
|
||||
}
|
||||
|
||||
if (!isDevMockEnabled() && args.force !== true) {
|
||||
console.error('当前是生产环境,已拒绝生成 mock 数据。如确实要执行,请追加 --force,或设置 ENABLE_DEV_MOCK=1。')
|
||||
console.error(
|
||||
'当前是生产环境,已拒绝生成 mock 数据。如确实要执行,请追加 --force,或设置 ENABLE_DEV_MOCK=1。',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@ const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(CURRENT_DIR, '..')
|
||||
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, '../..')
|
||||
|
||||
loadEnvFiles([
|
||||
path.join(WORKSPACE_ROOT, '.env'),
|
||||
path.join(PROJECT_ROOT, '.env'),
|
||||
])
|
||||
loadEnvFiles([path.join(WORKSPACE_ROOT, '.env'), path.join(PROJECT_ROOT, '.env')])
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
|
||||
@@ -24,7 +21,9 @@ if (args.help) {
|
||||
|
||||
const secret = readEnv('KAQUAN91_SECRET')
|
||||
const version = readEnv('KAQUAN91_VERSION') || '1.0'
|
||||
const baseUrl = String(args.baseUrl || process.env.OPEN91_MOCK_BASE_URL || 'http://127.0.0.1').replace(/\/$/, '')
|
||||
const baseUrl = String(
|
||||
args.baseUrl || process.env.OPEN91_MOCK_BASE_URL || 'http://127.0.0.1',
|
||||
).replace(/\/$/, '')
|
||||
const mode = String(args.mode || 'create').trim()
|
||||
|
||||
if (!secret || secret.length !== 32) {
|
||||
@@ -40,21 +39,22 @@ if (!['create', 'query'].includes(mode)) {
|
||||
const orderNo = String(args.orderNo || `MOCK91${Date.now()}`).trim()
|
||||
const timestamp = Number(args.timestamp || Math.floor(Date.now() / 1000))
|
||||
|
||||
const payload = mode === 'query'
|
||||
? {
|
||||
orderNo,
|
||||
timestamp,
|
||||
version,
|
||||
}
|
||||
: {
|
||||
buyNum: normalizePositiveInteger(args.buyNum, 1),
|
||||
callbackUrl: String(args.callbackUrl || ''),
|
||||
maxAmount: String(args.maxAmount || '0.01'),
|
||||
orderNo,
|
||||
productNo: String(args.productNo || '套装-浪漫天命').trim(),
|
||||
timestamp,
|
||||
version,
|
||||
}
|
||||
const payload =
|
||||
mode === 'query'
|
||||
? {
|
||||
orderNo,
|
||||
timestamp,
|
||||
version,
|
||||
}
|
||||
: {
|
||||
buyNum: normalizePositiveInteger(args.buyNum, 1),
|
||||
callbackUrl: String(args.callbackUrl || ''),
|
||||
maxAmount: String(args.maxAmount || '0.01'),
|
||||
orderNo,
|
||||
productNo: String(args.productNo || '套装-浪漫天命').trim(),
|
||||
timestamp,
|
||||
version,
|
||||
}
|
||||
|
||||
const body = {
|
||||
...payload,
|
||||
@@ -75,7 +75,9 @@ const response = await fetch(endpoint, {
|
||||
}).catch((error) => {
|
||||
console.error('请求 91 mock 接口失败,请确认后端入口可访问。')
|
||||
console.error(`当前地址:${endpoint}`)
|
||||
console.error('Docker 开发环境通常使用 http://127.0.0.1;本地直启后端通常使用 http://127.0.0.1:3000。')
|
||||
console.error(
|
||||
'Docker 开发环境通常使用 http://127.0.0.1;本地直启后端通常使用 http://127.0.0.1:3000。',
|
||||
)
|
||||
throw error
|
||||
})
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(CURRENT_DIR, '..')
|
||||
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, '../..')
|
||||
|
||||
loadEnvFiles([
|
||||
path.join(WORKSPACE_ROOT, '.env'),
|
||||
path.join(PROJECT_ROOT, '.env'),
|
||||
])
|
||||
loadEnvFiles([path.join(WORKSPACE_ROOT, '.env'), path.join(PROJECT_ROOT, '.env')])
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
|
||||
@@ -65,13 +62,21 @@ if (args.provider && providerPresets[String(args.provider)]) {
|
||||
console.log(` 预设环境: ${preset.description}`)
|
||||
}
|
||||
} else {
|
||||
baseUrl = String(args.baseUrl || process.env.KUASHOU_INDUSTRY_TEST_BASE_URL || 'http://127.0.0.1:3000').replace(/\/+$/, '')
|
||||
baseUrl = String(
|
||||
args.baseUrl || process.env.KUASHOU_INDUSTRY_TEST_BASE_URL || 'http://127.0.0.1:3000',
|
||||
).replace(/\/+$/, '')
|
||||
}
|
||||
|
||||
// 签名配置:CLI 参数 > 环境变量 > 默认值
|
||||
const appKey = (String(args.appKey || '') || process.env.KUASHOU_INDUSTRY_APP_KEY || '').trim()
|
||||
const signSecret = (String(args.signSecret || '') || process.env.KUASHOU_INDUSTRY_SIGN_SECRET || '').trim()
|
||||
const signMethod = (String(args.signMethod || 'MD5').trim().toUpperCase())
|
||||
const signSecret = (
|
||||
String(args.signSecret || '') ||
|
||||
process.env.KUASHOU_INDUSTRY_SIGN_SECRET ||
|
||||
''
|
||||
).trim()
|
||||
const signMethod = String(args.signMethod || 'MD5')
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
|
||||
const isLoadTest = Boolean(args.load)
|
||||
const targetTps = normalizePositiveInteger(args.tps, 100)
|
||||
@@ -119,24 +124,29 @@ if (!isLoadTest) {
|
||||
let failed = 0
|
||||
|
||||
// 1. 通知商家发码
|
||||
const sendParams = buildSignedParams(appKey, {
|
||||
oid: testOid,
|
||||
sellerId: '2174425348',
|
||||
num: testNum,
|
||||
itemId: '20842040339391',
|
||||
skuId: '87457167264391',
|
||||
itemTitle: '测试电子凭证商品',
|
||||
sendType: 'VIRTUAL',
|
||||
eticketType: 'DINING_OPEN_TICKET',
|
||||
token: `test-token-${testOid}`,
|
||||
certExpireType: 3,
|
||||
certExpDays: 30,
|
||||
certActualStartTime: Date.now(),
|
||||
certActualEndTime: Date.now() + 30 * 24 * 60 * 60 * 1000,
|
||||
}, signMethod)
|
||||
const sendParams = buildSignedParams(
|
||||
appKey,
|
||||
{
|
||||
oid: testOid,
|
||||
sellerId: '2174425348',
|
||||
num: testNum,
|
||||
itemId: '20842040339391',
|
||||
skuId: '87457167264391',
|
||||
itemTitle: '测试电子凭证商品',
|
||||
sendType: 'VIRTUAL',
|
||||
eticketType: 'DINING_OPEN_TICKET',
|
||||
token: `test-token-${testOid}`,
|
||||
certExpireType: 3,
|
||||
certExpDays: 30,
|
||||
certActualStartTime: Date.now(),
|
||||
certActualEndTime: Date.now() + 30 * 24 * 60 * 60 * 1000,
|
||||
},
|
||||
signMethod,
|
||||
)
|
||||
|
||||
const r1 = await post('/send-code', sendParams, { oid: testOid })
|
||||
if (assertResult(1, '通知商家发码', r1)) passed++; else failed++
|
||||
if (assertResult(1, '通知商家发码', r1)) passed++
|
||||
else failed++
|
||||
if (r1.ok) {
|
||||
const len = r1.data?.data?.etickets?.length || 0
|
||||
const firstId = r1.data?.data?.etickets?.[0]?.id
|
||||
@@ -145,12 +155,16 @@ if (!isLoadTest) {
|
||||
|
||||
// 2. 重复发码(幂等性)
|
||||
const r2 = await post('/send-code', sendParams, { oid: testOid })
|
||||
if (assertResult(2, '重复发码(幂等性)', r2)) passed++; else failed++
|
||||
if (assertResult(2, '重复发码(幂等性)', r2)) passed++
|
||||
else failed++
|
||||
|
||||
// 3. 查询全部卡券
|
||||
const qBiz: JsonObject = { oid: testOid, sendType: 'VIRTUAL', eticketType: 'DINING_OPEN_TICKET' }
|
||||
const r3 = await post('/query-code', buildSignedParams(appKey, qBiz, signMethod), { oid: testOid })
|
||||
if (assertResult(3, '查询全部卡券', r3)) passed++; else failed++
|
||||
const r3 = await post('/query-code', buildSignedParams(appKey, qBiz, signMethod), {
|
||||
oid: testOid,
|
||||
})
|
||||
if (assertResult(3, '查询全部卡券', r3)) passed++
|
||||
else failed++
|
||||
if (r3.ok) {
|
||||
console.log(` 卡券数: ${r3.data?.data?.etickets?.length || 0}`)
|
||||
r3.data?.data?.etickets?.forEach((e: JsonObject, i: number) => {
|
||||
@@ -161,13 +175,31 @@ if (!isLoadTest) {
|
||||
// 4. 查询单个卡券
|
||||
const firstEticketId = r3.data?.data?.etickets?.[0]?.id
|
||||
if (firstEticketId) {
|
||||
const r4 = await post('/query-code', buildSignedParams(appKey, { oid: testOid, eticketId: String(firstEticketId), sendType: 'VIRTUAL', eticketType: 'DINING_OPEN_TICKET' }, signMethod), { oid: testOid })
|
||||
if (assertResult(4, `查询单个卡券 (eticketId=${firstEticketId})`, r4)) passed++; else failed++
|
||||
const r4 = await post(
|
||||
'/query-code',
|
||||
buildSignedParams(
|
||||
appKey,
|
||||
{
|
||||
oid: testOid,
|
||||
eticketId: String(firstEticketId),
|
||||
sendType: 'VIRTUAL',
|
||||
eticketType: 'DINING_OPEN_TICKET',
|
||||
},
|
||||
signMethod,
|
||||
),
|
||||
{ oid: testOid },
|
||||
)
|
||||
if (assertResult(4, `查询单个卡券 (eticketId=${firstEticketId})`, r4)) passed++
|
||||
else failed++
|
||||
}
|
||||
|
||||
// 5. 查询不存在的订单
|
||||
const fakeOid = `FAKE_${Date.now()}`
|
||||
const r5 = await post('/query-code', buildSignedParams(appKey, { oid: fakeOid, sendType: 'VIRTUAL' }, signMethod), { oid: fakeOid })
|
||||
const r5 = await post(
|
||||
'/query-code',
|
||||
buildSignedParams(appKey, { oid: fakeOid, sendType: 'VIRTUAL' }, signMethod),
|
||||
{ oid: fakeOid },
|
||||
)
|
||||
const expect4012002 = r5.data?.result === 4012002
|
||||
if (expect4012002) {
|
||||
passed++
|
||||
@@ -179,24 +211,52 @@ if (!isLoadTest) {
|
||||
|
||||
// 6. 销毁指定卡券
|
||||
if (firstEticketId) {
|
||||
const r6 = await post('/destroy-code', buildSignedParams(appKey, { oid: testOid, reason: 'USER_APPLY_REFUND', etickets: [{ id: String(firstEticketId), num: 1, goodsValue: 100 }] }, signMethod), { oid: testOid })
|
||||
if (assertResult(6, `销毁卡券 (id=${firstEticketId})`, r6)) passed++; else failed++
|
||||
const r6 = await post(
|
||||
'/destroy-code',
|
||||
buildSignedParams(
|
||||
appKey,
|
||||
{
|
||||
oid: testOid,
|
||||
reason: 'USER_APPLY_REFUND',
|
||||
etickets: [{ id: String(firstEticketId), num: 1, goodsValue: 100 }],
|
||||
},
|
||||
signMethod,
|
||||
),
|
||||
{ oid: testOid },
|
||||
)
|
||||
if (assertResult(6, `销毁卡券 (id=${firstEticketId})`, r6)) passed++
|
||||
else failed++
|
||||
|
||||
// 7. 验证销毁后状态
|
||||
const r7 = await post('/query-code', buildSignedParams(appKey, { oid: testOid, eticketId: String(firstEticketId), sendType: 'VIRTUAL' }, signMethod), { oid: testOid })
|
||||
const r7 = await post(
|
||||
'/query-code',
|
||||
buildSignedParams(
|
||||
appKey,
|
||||
{ oid: testOid, eticketId: String(firstEticketId), sendType: 'VIRTUAL' },
|
||||
signMethod,
|
||||
),
|
||||
{ oid: testOid },
|
||||
)
|
||||
const destroyed = r7.data?.result === 1 && r7.data?.data?.etickets?.[0]?.status === 'DESTROYED'
|
||||
if (destroyed) {
|
||||
passed++
|
||||
console.log(` ✅ [7] 销毁后验证: status=DESTROYED`)
|
||||
} else {
|
||||
failed++
|
||||
console.log(` ❌ [7] 销毁后验证: 期望 DESTROYED 实际 ${r7.data?.data?.etickets?.[0]?.status}`)
|
||||
console.log(
|
||||
` ❌ [7] 销毁后验证: 期望 DESTROYED 实际 ${r7.data?.data?.etickets?.[0]?.status}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 整单销毁
|
||||
const r8 = await post('/destroy-code', buildSignedParams(appKey, { oid: testOid, reason: 'ETICKET_EXPIRED' }, signMethod), { oid: testOid })
|
||||
if (assertResult(8, '整单销毁', r8)) passed++; else failed++
|
||||
const r8 = await post(
|
||||
'/destroy-code',
|
||||
buildSignedParams(appKey, { oid: testOid, reason: 'ETICKET_EXPIRED' }, signMethod),
|
||||
{ oid: testOid },
|
||||
)
|
||||
if (assertResult(8, '整单销毁', r8)) passed++
|
||||
else failed++
|
||||
|
||||
// 9. 签名错误测试
|
||||
const badParams = buildSignedParams(appKey, { oid: testOid, sendType: 'VIRTUAL' }, signMethod)
|
||||
@@ -213,7 +273,9 @@ if (!isLoadTest) {
|
||||
|
||||
console.log('')
|
||||
console.log('╔══════════════════════════════════════════════════════╗')
|
||||
console.log(`║ HTTP 功能测试: ${String(passed).padStart(2)}/${passed + failed} 通过, ${String(failed).padStart(2)}/${passed + failed} 失败 ║`)
|
||||
console.log(
|
||||
`║ HTTP 功能测试: ${String(passed).padStart(2)}/${passed + failed} 通过, ${String(failed).padStart(2)}/${passed + failed} 失败 ║`,
|
||||
)
|
||||
console.log('╚══════════════════════════════════════════════════════╝')
|
||||
console.log('')
|
||||
process.exit(failed > 0 ? 1 : 0)
|
||||
@@ -222,35 +284,39 @@ if (!isLoadTest) {
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 压力测试
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const endpointsToTest = testEndpoint
|
||||
? [testEndpoint]
|
||||
: ['send-code', 'query-code', 'destroy-code']
|
||||
const endpointsToTest = testEndpoint ? [testEndpoint] : ['send-code', 'query-code', 'destroy-code']
|
||||
|
||||
const loadTestOid = `LOAD_${Date.now()}`
|
||||
|
||||
// 先发码创建订单以供查询/销毁
|
||||
console.log('')
|
||||
console.log(` 准备测试数据: 通知商家发码 (oid=${loadTestOid})...`)
|
||||
const prepParams = buildSignedParams(appKey, {
|
||||
oid: loadTestOid,
|
||||
sellerId: '2174425348',
|
||||
num: 10,
|
||||
itemId: '20842040339391',
|
||||
skuId: '87457167264391',
|
||||
itemTitle: '压测商品',
|
||||
sendType: 'VIRTUAL',
|
||||
eticketType: 'DINING_OPEN_TICKET',
|
||||
token: `load-test-token-${loadTestOid}`,
|
||||
certExpireType: 3,
|
||||
certExpDays: 30,
|
||||
certActualStartTime: Date.now(),
|
||||
certActualEndTime: Date.now() + 30 * 24 * 60 * 60 * 1000,
|
||||
}, signMethod)
|
||||
const prepParams = buildSignedParams(
|
||||
appKey,
|
||||
{
|
||||
oid: loadTestOid,
|
||||
sellerId: '2174425348',
|
||||
num: 10,
|
||||
itemId: '20842040339391',
|
||||
skuId: '87457167264391',
|
||||
itemTitle: '压测商品',
|
||||
sendType: 'VIRTUAL',
|
||||
eticketType: 'DINING_OPEN_TICKET',
|
||||
token: `load-test-token-${loadTestOid}`,
|
||||
certExpireType: 3,
|
||||
certExpDays: 30,
|
||||
certActualStartTime: Date.now(),
|
||||
certActualEndTime: Date.now() + 30 * 24 * 60 * 60 * 1000,
|
||||
},
|
||||
signMethod,
|
||||
)
|
||||
const prepResult = await post('/send-code', prepParams, {})
|
||||
if (prepResult.data?.result === 1) {
|
||||
console.log(` 数据准备完成 (${prepResult.ms}ms)`)
|
||||
} else {
|
||||
console.log(` ⚠ 数据准备失败: result=${prepResult.data?.result} error_msg="${prepResult.data?.error_msg}" (${prepResult.ms}ms)`)
|
||||
console.log(
|
||||
` ⚠ 数据准备失败: result=${prepResult.data?.result} error_msg="${prepResult.data?.error_msg}" (${prepResult.ms}ms)`,
|
||||
)
|
||||
console.log(` 请确认服务端已配置正确的 appKey/signSecret 且未触发限流`)
|
||||
}
|
||||
|
||||
@@ -258,14 +324,18 @@ for (const endpoint of endpointsToTest) {
|
||||
console.log('')
|
||||
console.log('╔══════════════════════════════════════════════════════╗')
|
||||
console.log(`║ 压测: ${endpoint.padEnd(46)}║`)
|
||||
console.log(`║ 目标: ${String(targetTps).padEnd(4)} TPS, 持续 ${String(testDuration).padEnd(3)}s ║`)
|
||||
console.log(
|
||||
`║ 目标: ${String(targetTps).padEnd(4)} TPS, 持续 ${String(testDuration).padEnd(3)}s ║`,
|
||||
)
|
||||
console.log('╚══════════════════════════════════════════════════════╝')
|
||||
|
||||
const result = await runLoadTest(endpoint, targetTps, testDuration, loadTestOid)
|
||||
console.log(` 成功 TPS : ${result.successTps.toFixed(1)} (业务 result=1)`)
|
||||
console.log(` 总请求数 : ${result.totalReqs}`)
|
||||
console.log(` 业务成功 : ${result.successes}`)
|
||||
console.log(` 业务失败 : ${result.bizErrors}${result.bizSample ? ` (示例: ${result.bizSample})` : ''}`)
|
||||
console.log(
|
||||
` 业务失败 : ${result.bizErrors}${result.bizSample ? ` (示例: ${result.bizSample})` : ''}`,
|
||||
)
|
||||
console.log(` 被限流 : ${result.rateLimited}`)
|
||||
console.log(` 平均耗时 : ${result.avgMs.toFixed(1)} ms`)
|
||||
console.log(` P50 : ${result.p50Ms.toFixed(1)} ms`)
|
||||
@@ -275,7 +345,9 @@ for (const endpoint of endpointsToTest) {
|
||||
console.log(` 最大耗时 : ${result.maxMs.toFixed(1)} ms`)
|
||||
|
||||
if (result.rateLimited > result.totalReqs * 0.1) {
|
||||
console.log(` ⚠ 注意: ${((result.rateLimited / result.totalReqs) * 100).toFixed(0)}% 请求被限流,建议调高 KUASHOU_INDUSTRY_RATE_LIMIT_MAX`)
|
||||
console.log(
|
||||
` ⚠ 注意: ${((result.rateLimited / result.totalReqs) * 100).toFixed(0)}% 请求被限流,建议调高 KUASHOU_INDUSTRY_RATE_LIMIT_MAX`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +383,11 @@ async function post(
|
||||
})
|
||||
status = res.status
|
||||
const text = await res.text()
|
||||
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
||||
try {
|
||||
data = JSON.parse(text)
|
||||
} catch {
|
||||
data = { raw: text }
|
||||
}
|
||||
ok = res.ok && data.result === 1
|
||||
} catch (err) {
|
||||
data = { error: String(err) }
|
||||
@@ -455,9 +531,7 @@ function signKuaishouIndustry(params: JsonObject, method: string): string {
|
||||
.filter(([key]) => key !== 'sign' && key !== 'signSecret')
|
||||
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
||||
|
||||
const queryString = entries
|
||||
.map(([key, value]) => `${key}=${stringifySignVal(value)}`)
|
||||
.join('&')
|
||||
const queryString = entries.map(([key, value]) => `${key}=${stringifySignVal(value)}`).join('&')
|
||||
|
||||
const source = `${queryString}&signSecret=${signSecret}`
|
||||
|
||||
@@ -472,23 +546,30 @@ function stringifySignVal(value: unknown): string {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) return String(value)
|
||||
if (typeof value === 'boolean') return value ? 'true' : 'false'
|
||||
if (value == null) return ''
|
||||
if (typeof value === 'object') return JSON.stringify(value, Object.keys(value as JsonObject).sort())
|
||||
if (typeof value === 'object')
|
||||
return JSON.stringify(value, Object.keys(value as JsonObject).sort())
|
||||
return String(value)
|
||||
}
|
||||
|
||||
// ─────────────────── 工具 ───────────────────
|
||||
|
||||
function assertResult(num: number, label: string, r: { ok: boolean; status: number; data: JsonObject; ms: number }) {
|
||||
function assertResult(
|
||||
num: number,
|
||||
label: string,
|
||||
r: { ok: boolean; status: number; data: JsonObject; ms: number },
|
||||
) {
|
||||
if (r.ok) {
|
||||
console.log(` ✅ [${num}] ${label}: result=1 (${r.ms}ms)`)
|
||||
return true
|
||||
}
|
||||
console.log(` ❌ [${num}] ${label}: HTTP ${r.status} result=${r.data?.result} error_msg="${r.data?.error_msg}" (${r.ms}ms)`)
|
||||
console.log(
|
||||
` ❌ [${num}] ${label}: HTTP ${r.status} result=${r.data?.result} error_msg="${r.data?.error_msg}" (${r.ms}ms)`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function avg(arr: number[]): number {
|
||||
@@ -512,10 +593,16 @@ function parseArgs(rawArgs: string[]): JsonObject {
|
||||
for (const rawArg of rawArgs) {
|
||||
const arg = String(rawArg || '').trim()
|
||||
if (!arg) continue
|
||||
if (arg === '--help' || arg === '-h') { parsed.help = true; continue }
|
||||
if (arg === '--help' || arg === '-h') {
|
||||
parsed.help = true
|
||||
continue
|
||||
}
|
||||
const normalized = arg.startsWith('--') ? arg.slice(2) : arg
|
||||
const eqIdx = normalized.indexOf('=')
|
||||
if (eqIdx < 0) { parsed[normalized] = true; continue }
|
||||
if (eqIdx < 0) {
|
||||
parsed[normalized] = true
|
||||
continue
|
||||
}
|
||||
const key = normalized.slice(0, eqIdx).trim()
|
||||
const value = normalized.slice(eqIdx + 1).trim()
|
||||
if (key) parsed[key] = value
|
||||
|
||||
@@ -21,13 +21,12 @@ const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PROJECT_ROOT = path.resolve(CURRENT_DIR, '..')
|
||||
const WORKSPACE_ROOT = path.resolve(PROJECT_ROOT, '../..')
|
||||
|
||||
loadEnvFiles([
|
||||
path.join(WORKSPACE_ROOT, '.env'),
|
||||
path.join(PROJECT_ROOT, '.env'),
|
||||
])
|
||||
loadEnvFiles([path.join(WORKSPACE_ROOT, '.env'), path.join(PROJECT_ROOT, '.env')])
|
||||
|
||||
process.env.KUASHOU_INDUSTRY_APP_KEY = process.env.KUASHOU_INDUSTRY_APP_KEY || 'test_industry_app_key'
|
||||
process.env.KUASHOU_INDUSTRY_SIGN_SECRET = process.env.KUASHOU_INDUSTRY_SIGN_SECRET || 'test_industry_sign_secret'
|
||||
process.env.KUASHOU_INDUSTRY_APP_KEY =
|
||||
process.env.KUASHOU_INDUSTRY_APP_KEY || 'test_industry_app_key'
|
||||
process.env.KUASHOU_INDUSTRY_SIGN_SECRET =
|
||||
process.env.KUASHOU_INDUSTRY_SIGN_SECRET || 'test_industry_sign_secret'
|
||||
process.env.KUASHOU_INDUSTRY_SHOP_ID = process.env.KUASHOU_INDUSTRY_SHOP_ID || 'test_shop'
|
||||
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
@@ -56,7 +55,9 @@ const [
|
||||
const config = getKuaishouIndustryConfig()
|
||||
const testOid = String(args.oid || `TEST_KS${Date.now()}`).trim()
|
||||
const testNum = normalizePositiveInteger(args.num, 3)
|
||||
const signMethod = String(args.signMethod || 'MD5').trim().toUpperCase()
|
||||
const signMethod = String(args.signMethod || 'MD5')
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
|
||||
console.log('')
|
||||
console.log('╔══════════════════════════════════════════════════════╗')
|
||||
@@ -149,7 +150,9 @@ try {
|
||||
console.log(` 已发货数量: ${queryCodeResult.data?.sendNum || 0}`)
|
||||
console.log(` 卡券列表数: ${queryCodeResult.data?.etickets?.length || 0}`)
|
||||
queryCodeResult.data?.etickets?.forEach((eticket: Record<string, unknown>, index: number) => {
|
||||
console.log(` [${index + 1}] id=${eticket.id} status=${eticket.status} num=${eticket.num}`)
|
||||
console.log(
|
||||
` [${index + 1}] id=${eticket.id} status=${eticket.status} num=${eticket.num}`,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
failed++
|
||||
@@ -206,7 +209,9 @@ try {
|
||||
console.log(` 错误信息: ${queryMissingResult.error_msg}`)
|
||||
} else {
|
||||
failed++
|
||||
console.log(` └─ ❌ 查询不存在订单: 期望 result=4012002,实际 result=${queryMissingResult.result}`)
|
||||
console.log(
|
||||
` └─ ❌ 查询不存在订单: 期望 result=4012002,实际 result=${queryMissingResult.result}`,
|
||||
)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
@@ -214,11 +219,13 @@ try {
|
||||
// ─────────────────────────────────────────────────────────
|
||||
if (queryCodeResult.data?.etickets?.length > 0) {
|
||||
total++
|
||||
const destroyTargetIds = queryCodeResult.data.etickets.slice(0, 1).map((e: Record<string, unknown>) => ({
|
||||
id: String(e.id),
|
||||
num: 1,
|
||||
goodsValue: 100,
|
||||
}))
|
||||
const destroyTargetIds = queryCodeResult.data.etickets
|
||||
.slice(0, 1)
|
||||
.map((e: Record<string, unknown>) => ({
|
||||
id: String(e.id),
|
||||
num: 1,
|
||||
goodsValue: 100,
|
||||
}))
|
||||
printStep(6, `通知商家销毁(指定卡券 id=${destroyTargetIds[0].id})`)
|
||||
|
||||
const destroyBizParams: Record<string, unknown> = {
|
||||
@@ -248,7 +255,11 @@ try {
|
||||
eticketType: 'DINING_OPEN_TICKET',
|
||||
}
|
||||
|
||||
const verifyDestroyParams = buildRequestParams(config.appKey, verifyDestroyBizParams, signMethod)
|
||||
const verifyDestroyParams = buildRequestParams(
|
||||
config.appKey,
|
||||
verifyDestroyBizParams,
|
||||
signMethod,
|
||||
)
|
||||
const verifyDestroyResult = await handleQueryCode(verifyDestroyParams)
|
||||
|
||||
printResult('销毁后查询', verifyDestroyResult)
|
||||
@@ -258,7 +269,9 @@ try {
|
||||
console.log(` 卡券状态: ${verifyStatus}`)
|
||||
} else {
|
||||
failed++
|
||||
console.log(` 期望 status=DESTROYED,实际 status=${verifyStatus}, result=${verifyDestroyResult.result}`)
|
||||
console.log(
|
||||
` 期望 status=DESTROYED,实际 status=${verifyStatus}, result=${verifyDestroyResult.result}`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
console.log(' ⚠ 跳过(无卡券可销毁)')
|
||||
@@ -284,7 +297,6 @@ try {
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
|
||||
} finally {
|
||||
await closeDb()
|
||||
}
|
||||
@@ -292,7 +304,9 @@ try {
|
||||
// ─────────────────────────────────────────────────────────
|
||||
console.log('')
|
||||
console.log('╔══════════════════════════════════════════════════════╗')
|
||||
console.log(`║ 测试完成: ${String(passed).padStart(2)}/${total} 通过, ${String(failed).padStart(2)}/${total} 失败 ║`)
|
||||
console.log(
|
||||
`║ 测试完成: ${String(passed).padStart(2)}/${total} 通过, ${String(failed).padStart(2)}/${total} 失败 ║`,
|
||||
)
|
||||
console.log('╚══════════════════════════════════════════════════════╝')
|
||||
console.log('')
|
||||
|
||||
@@ -338,7 +352,9 @@ function printStep(stepNum: number, title: string) {
|
||||
function printResult(label: string, result: Record<string, unknown>) {
|
||||
const ok = result.result === 1
|
||||
const icon = ok ? '✅' : '❌'
|
||||
const resultText = ok ? `result=${result.result}` : `result=${result.result} error_msg="${result.error_msg}"`
|
||||
const resultText = ok
|
||||
? `result=${result.result}`
|
||||
: `result=${result.result} error_msg="${result.error_msg}"`
|
||||
console.log(` └─ ${icon} ${label}: ${resultText}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,18 @@ async function main() {
|
||||
const config = runtimeConfig.worker.sms
|
||||
console.log('当前短信配置:')
|
||||
console.log(' provider :', config.provider)
|
||||
console.log(' accessKeyId :', config.accessKeyId ? `${config.accessKeyId.slice(0, 4)}...${config.accessKeyId.slice(-4)}` : '(空)')
|
||||
console.log(' accessKeySecret :', config.accessKeySecret ? `${config.accessKeySecret.slice(0, 4)}...${config.accessKeySecret.slice(-4)}(长度 ${config.accessKeySecret.length})` : '(空)')
|
||||
console.log(
|
||||
' accessKeyId :',
|
||||
config.accessKeyId
|
||||
? `${config.accessKeyId.slice(0, 4)}...${config.accessKeyId.slice(-4)}`
|
||||
: '(空)',
|
||||
)
|
||||
console.log(
|
||||
' accessKeySecret :',
|
||||
config.accessKeySecret
|
||||
? `${config.accessKeySecret.slice(0, 4)}...${config.accessKeySecret.slice(-4)}(长度 ${config.accessKeySecret.length})`
|
||||
: '(空)',
|
||||
)
|
||||
console.log(' signName :', config.signName)
|
||||
console.log(' templateCode :', config.templateCode)
|
||||
console.log('')
|
||||
@@ -64,13 +74,18 @@ async function main() {
|
||||
const url = `https://dysmsapi.aliyuncs.com/?${queryString}`
|
||||
|
||||
console.log('stringToSign:')
|
||||
console.log(' ', `GET&%2F&${percentEncode(
|
||||
Object.entries(params)
|
||||
.filter(([key]) => key !== 'Signature')
|
||||
.map(([key, value]) => `${percentEncode(key)}=${percentEncode(value)}`)
|
||||
.sort((left, right) => (String(left[0]) < String(right[0]) ? -1 : String(left[0]) > String(right[0]) ? 1 : 0))
|
||||
.join('&'),
|
||||
)}`)
|
||||
console.log(
|
||||
' ',
|
||||
`GET&%2F&${percentEncode(
|
||||
Object.entries(params)
|
||||
.filter(([key]) => key !== 'Signature')
|
||||
.map(([key, value]) => `${percentEncode(key)}=${percentEncode(value)}`)
|
||||
.sort((left, right) =>
|
||||
String(left[0]) < String(right[0]) ? -1 : String(left[0]) > String(right[0]) ? 1 : 0,
|
||||
)
|
||||
.join('&'),
|
||||
)}`,
|
||||
)
|
||||
console.log('')
|
||||
console.log(`发送测试短信到 ${phone}(验证码 123456)...`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user