完整流程加节流 + 高频重试
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"baseUrl": "https://123.207.217.176",
|
"baseUrl": "https://123.207.217.176",
|
||||||
"username": "17665234375",
|
"username": "17665234375",
|
||||||
"phone": "17665234375",
|
"phone": "17665234375",
|
||||||
"loggedInAt": "2026-05-02T14:56:41.055Z",
|
"loggedInAt": "2026-05-02T15:19:01.258Z",
|
||||||
"deviceId": "-",
|
"deviceId": "-",
|
||||||
"deviceType": 0
|
"deviceType": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"syncFromCreatedAt": "2026-05-02T10:53:28.550Z",
|
"syncFromCreatedAt": "2026-05-02T10:53:28.550Z",
|
||||||
"lastRunStartedAt": "2026-05-02T15:00:54.051Z",
|
"lastRunStartedAt": "2026-05-02T16:37:16.806Z",
|
||||||
"lastRunFinishedAt": "2026-05-02T15:00:55.112Z",
|
"lastRunFinishedAt": "2026-05-02T16:37:17.707Z",
|
||||||
"lastRunStatus": "success",
|
"lastRunStatus": "success",
|
||||||
"lastErrorMessage": "",
|
"lastErrorMessage": "",
|
||||||
"fetchedCount": 40,
|
"fetchedCount": 40,
|
||||||
"syncedCount": 0,
|
"syncedCount": 0,
|
||||||
"ignoredCount": 40,
|
"ignoredCount": 40,
|
||||||
"lastOrderCreatedAt": "2026-05-02T15:00:47.000Z"
|
"lastOrderCreatedAt": "2026-05-02T16:36:46.000Z"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
listAdminCloudtentaclesVirtualNumbers,
|
listAdminCloudtentaclesVirtualNumbers,
|
||||||
lookupAdminFulfillmentBindingOrder,
|
lookupAdminFulfillmentBindingOrder,
|
||||||
queryAdminKhhaoOrders,
|
queryAdminKhhaoOrders,
|
||||||
|
runAdminCloudtentaclesFullFlow,
|
||||||
sendAdminCloudtentaclesSmsCode,
|
sendAdminCloudtentaclesSmsCode,
|
||||||
syncAdminKhhaoOrders,
|
syncAdminKhhaoOrders,
|
||||||
testAdminCloudtentaclesLogin,
|
testAdminCloudtentaclesLogin,
|
||||||
@@ -45,6 +46,7 @@ import { createJsonHandler, requireAdminRoles } from './shared.js'
|
|||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesCatalogQueryRouteBody} AdminCloudtentaclesCatalogQueryRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesCatalogQueryRouteBody} AdminCloudtentaclesCatalogQueryRouteBody */
|
||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesSkuBuyRouteBody} AdminCloudtentaclesSkuBuyRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesSkuBuyRouteBody} AdminCloudtentaclesSkuBuyRouteBody */
|
||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesVirtualNumberRouteBody} AdminCloudtentaclesVirtualNumberRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesVirtualNumberRouteBody} AdminCloudtentaclesVirtualNumberRouteBody */
|
||||||
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesFullFlowRouteBody} AdminCloudtentaclesFullFlowRouteBody */
|
||||||
/** @typedef {import('../../types/admin-write-models.js').AdminAgisoShopConfigSaveResponse} AdminAgisoShopConfigSaveResponse */
|
/** @typedef {import('../../types/admin-write-models.js').AdminAgisoShopConfigSaveResponse} AdminAgisoShopConfigSaveResponse */
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
@@ -381,6 +383,15 @@ router.post('/platform-config/cloudtentacles/vn/bind-url', createJsonHandler(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
||||||
|
router.post('/platform-config/cloudtentacles/debug/full-flow', createJsonHandler(
|
||||||
|
(req) => runAdminCloudtentaclesFullFlow(/** @type {AdminCloudtentaclesFullFlowRouteBody} */ (req.body)),
|
||||||
|
{
|
||||||
|
successMessage: 'cloudtentacles 完整调试流程执行成功',
|
||||||
|
errorMessage: 'cloudtentacles 完整调试流程执行失败',
|
||||||
|
scope: '[admin/platform-config/cloudtentacles/debug/full-flow]',
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
router.get('/platform-config/fulfillment-bindings', createJsonHandler(
|
router.get('/platform-config/fulfillment-bindings', createJsonHandler(
|
||||||
() => getAdminFulfillmentBindingConfigs(),
|
() => getAdminFulfillmentBindingConfigs(),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
listCloudtentaclesVirtualNumbers,
|
listCloudtentaclesVirtualNumbers,
|
||||||
verifyCloudtentaclesLoginCode,
|
verifyCloudtentaclesLoginCode,
|
||||||
} from '../platforms/cloudtentacles/virtual-number-service.js'
|
} from '../platforms/cloudtentacles/virtual-number-service.js'
|
||||||
|
import { runCloudtentaclesFullDebugFlow } from '../platforms/cloudtentacles/debug-flow-service.js'
|
||||||
import { normalizeAgisoMessageTemplate } from '../platforms/agiso/xianyu/message-service.js'
|
import { normalizeAgisoMessageTemplate } from '../platforms/agiso/xianyu/message-service.js'
|
||||||
import {
|
import {
|
||||||
getOrderFulfillmentBindingConfigs,
|
getOrderFulfillmentBindingConfigs,
|
||||||
@@ -70,6 +71,7 @@ import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
|||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesCatalogQueryInput} AdminCloudtentaclesCatalogQueryInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesCatalogQueryInput} AdminCloudtentaclesCatalogQueryInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesSkuBuyInput} AdminCloudtentaclesSkuBuyInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesSkuBuyInput} AdminCloudtentaclesSkuBuyInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesVirtualNumberInput} AdminCloudtentaclesVirtualNumberInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesVirtualNumberInput} AdminCloudtentaclesVirtualNumberInput */
|
||||||
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesFullFlowInput} AdminCloudtentaclesFullFlowInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingLookupInput} AdminFulfillmentBindingLookupInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingLookupInput} AdminFulfillmentBindingLookupInput */
|
||||||
|
|
||||||
@@ -609,6 +611,17 @@ export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCl
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {AdminCloudtentaclesFullFlowInput} [payload] */
|
||||||
|
export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) {
|
||||||
|
const context = resolveCloudtentaclesAdminContext(payload)
|
||||||
|
return runCloudtentaclesFullDebugFlow({
|
||||||
|
...context,
|
||||||
|
skuId: payload.skuId,
|
||||||
|
skuCount: payload.skuCount,
|
||||||
|
vnKey: payload.vnKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAdminFulfillmentBindingConfigs() {
|
export async function getAdminFulfillmentBindingConfigs() {
|
||||||
const bindings = getOrderFulfillmentBindingConfigs()
|
const bindings = getOrderFulfillmentBindingConfigs()
|
||||||
const rowsResult = await query(
|
const rowsResult = await query(
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import { createHttpError } from '../../../utils/http.js'
|
||||||
|
import { getCloudtentaclesAsset, listCloudtentaclesSku, buyCloudtentaclesSku } from './catalog-service.js'
|
||||||
|
import { getCloudtentaclesKnapsack } from './knapsack-service.js'
|
||||||
|
import {
|
||||||
|
appointCloudtentaclesVirtualNumber,
|
||||||
|
fetchCloudtentaclesVirtualNumberCode,
|
||||||
|
generateCloudtentaclesLoginCode,
|
||||||
|
getCloudtentaclesBindUrl,
|
||||||
|
verifyCloudtentaclesLoginCode,
|
||||||
|
} from './virtual-number-service.js'
|
||||||
|
|
||||||
|
export async function runCloudtentaclesFullDebugFlow(payload = {}) {
|
||||||
|
const skuId = normalizePositiveInteger(payload.skuId, 0)
|
||||||
|
const skuCount = normalizePositiveInteger(payload.skuCount, 1)
|
||||||
|
const vnKey = String(payload.vnKey || '').trim()
|
||||||
|
|
||||||
|
if (!skuId) {
|
||||||
|
throw createHttpError('cloudtentacles 完整流程缺少 SKU ID', {
|
||||||
|
statusCode: 400,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_missing_sku_id',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vnKey) {
|
||||||
|
throw createHttpError('cloudtentacles 完整流程缺少 VN Key', {
|
||||||
|
statusCode: 400,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_missing_vn_key',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeAsset = await runFlowStep('查询购买前余额', () => getCloudtentaclesAsset(payload))
|
||||||
|
await sleep(350)
|
||||||
|
const skuList = await runFlowStep('查询 SKU 列表', () => listCloudtentaclesSku(payload))
|
||||||
|
const targetSku = skuList.items.find((item) => Number(item.id || 0) === skuId) || null
|
||||||
|
|
||||||
|
if (!targetSku) {
|
||||||
|
throw createHttpError(`cloudtentacles 未找到 SKU ${skuId}`, {
|
||||||
|
statusCode: 404,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_sku_not_found',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredAsset = Number(targetSku.price || 0) * skuCount
|
||||||
|
if (beforeAsset.asset < requiredAsset) {
|
||||||
|
throw createHttpError(`余额不足,当前 ${beforeAsset.asset},需要 ${requiredAsset}`, {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_insufficient_asset',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(350)
|
||||||
|
const beforeKnapsack = await runFlowStep('查询购买前背包', () => getCloudtentaclesKnapsack(payload))
|
||||||
|
const beforeItem = findKnapsackItem(beforeKnapsack.items, skuId)
|
||||||
|
|
||||||
|
await sleep(500)
|
||||||
|
const buyResult = await runFlowStep('购买 SKU', () => buyCloudtentaclesSku({
|
||||||
|
...payload,
|
||||||
|
id: skuId,
|
||||||
|
count: skuCount,
|
||||||
|
}), { retries: 1, retryDelayMs: 1200 })
|
||||||
|
|
||||||
|
await sleep(1200)
|
||||||
|
const afterAsset = await runFlowStep('查询购买后余额', () => getCloudtentaclesAsset(payload), {
|
||||||
|
retries: 2,
|
||||||
|
retryDelayMs: 1200,
|
||||||
|
})
|
||||||
|
await sleep(1200)
|
||||||
|
const afterKnapsack = await runFlowStep('查询购买后背包', () => getCloudtentaclesKnapsack(payload), {
|
||||||
|
retries: 2,
|
||||||
|
retryDelayMs: 1200,
|
||||||
|
})
|
||||||
|
const afterItem = findKnapsackItem(afterKnapsack.items, skuId)
|
||||||
|
|
||||||
|
const beforeCount = Number(beforeItem?.count || 0)
|
||||||
|
const afterCount = Number(afterItem?.count || 0)
|
||||||
|
const knapsackIncreased = afterCount >= beforeCount + skuCount
|
||||||
|
|
||||||
|
if (!knapsackIncreased) {
|
||||||
|
throw createHttpError(`购买后背包校验失败,购买前 ${beforeCount},购买后 ${afterCount},期望至少 ${beforeCount + skuCount}`, {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_knapsack_not_updated',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(700)
|
||||||
|
const appointed = await runFlowStep('申请虚拟号', () => appointCloudtentaclesVirtualNumber({
|
||||||
|
...payload,
|
||||||
|
key: vnKey,
|
||||||
|
}), { retries: 1, retryDelayMs: 1200 })
|
||||||
|
const appointedId = Number(appointed.item?.id || 0)
|
||||||
|
const appointedPhone = String(appointed.item?.phone || '').trim()
|
||||||
|
|
||||||
|
if (!appointedId || !appointedPhone) {
|
||||||
|
throw createHttpError('cloudtentacles 申请虚拟号成功但返回数据不完整', {
|
||||||
|
statusCode: 502,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_invalid_appointed_vn',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(1200)
|
||||||
|
const generateCodeResult = await runFlowStep('生成登录码', () => generateCloudtentaclesLoginCode({
|
||||||
|
...payload,
|
||||||
|
key: vnKey,
|
||||||
|
id: appointedId,
|
||||||
|
}), { retries: 1, retryDelayMs: 1200 })
|
||||||
|
|
||||||
|
await sleep(1600)
|
||||||
|
const fetchedCode = await runFlowStep('获取验证码', () => fetchCloudtentaclesVirtualNumberCode({
|
||||||
|
...payload,
|
||||||
|
key: vnKey,
|
||||||
|
phone: appointedPhone,
|
||||||
|
}), { retries: 2, retryDelayMs: 1500 })
|
||||||
|
|
||||||
|
await sleep(1000)
|
||||||
|
const verified = await runFlowStep('校验验证码', () => verifyCloudtentaclesLoginCode({
|
||||||
|
...payload,
|
||||||
|
key: vnKey,
|
||||||
|
id: appointedId,
|
||||||
|
code: fetchedCode.code,
|
||||||
|
}), { retries: 1, retryDelayMs: 1200 })
|
||||||
|
|
||||||
|
await sleep(1000)
|
||||||
|
const bindUrlResult = await runFlowStep('获取兑换链接', () => getCloudtentaclesBindUrl({
|
||||||
|
...payload,
|
||||||
|
key: vnKey,
|
||||||
|
id: appointedId,
|
||||||
|
}), { retries: 1, retryDelayMs: 1200 })
|
||||||
|
|
||||||
|
return {
|
||||||
|
sku: {
|
||||||
|
id: skuId,
|
||||||
|
count: skuCount,
|
||||||
|
name: String(targetSku.name || '').trim(),
|
||||||
|
price: Number(targetSku.price || 0),
|
||||||
|
requiredAsset,
|
||||||
|
},
|
||||||
|
assetCheck: {
|
||||||
|
before: beforeAsset.asset,
|
||||||
|
after: afterAsset.asset,
|
||||||
|
enough: beforeAsset.asset >= requiredAsset,
|
||||||
|
spent: beforeAsset.asset - afterAsset.asset,
|
||||||
|
},
|
||||||
|
knapsackCheck: {
|
||||||
|
beforeCount,
|
||||||
|
afterCount,
|
||||||
|
expectedIncrease: skuCount,
|
||||||
|
increased: knapsackIncreased,
|
||||||
|
},
|
||||||
|
purchase: buyResult,
|
||||||
|
virtualNumber: {
|
||||||
|
key: vnKey,
|
||||||
|
id: appointedId,
|
||||||
|
phone: appointedPhone,
|
||||||
|
appoint: appointed,
|
||||||
|
generateLoginCode: generateCodeResult,
|
||||||
|
fetchedCode,
|
||||||
|
verify: verified,
|
||||||
|
},
|
||||||
|
bindUrl: bindUrlResult.bindUrl,
|
||||||
|
bindUrlResult,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePositiveInteger(value, fallback) {
|
||||||
|
const parsed = Number(value)
|
||||||
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function findKnapsackItem(items, skuId) {
|
||||||
|
return (Array.isArray(items) ? items : []).find((item) => Number(item.id || 0) === Number(skuId)) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runFlowStep(label, runner, options = {}) {
|
||||||
|
const retries = Number.isInteger(options.retries) && options.retries > 0 ? options.retries : 0
|
||||||
|
const retryDelayMs = Number.isFinite(options.retryDelayMs) && options.retryDelayMs > 0 ? options.retryDelayMs : 1000
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
||||||
|
try {
|
||||||
|
return await runner()
|
||||||
|
} catch (error) {
|
||||||
|
if (attempt >= retries || !isHighFrequencyError(error)) {
|
||||||
|
throw wrapStepError(label, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(retryDelayMs * (attempt + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw createHttpError(`cloudtentacles ${label}失败`, {
|
||||||
|
statusCode: 500,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_step_failed',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHighFrequencyError(error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error || '')
|
||||||
|
return message.toLowerCase().includes('high-frequency request')
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapStepError(label, error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error || '未知错误')
|
||||||
|
|
||||||
|
if (isHighFrequencyError(error)) {
|
||||||
|
return createHttpError(`cloudtentacles ${label}失败:请求过于频繁,请稍后重试`, {
|
||||||
|
statusCode: 429,
|
||||||
|
errorCode: 'cloudtentacles_rate_limited',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && typeof error === 'object' && 'statusCode' in error) {
|
||||||
|
const statusCode = Number(Reflect.get(error, 'statusCode') || 500)
|
||||||
|
const errorCode = String(Reflect.get(error, 'errorCode') || 'cloudtentacles_full_flow_step_failed')
|
||||||
|
|
||||||
|
return createHttpError(`cloudtentacles ${label}失败:${message}`, {
|
||||||
|
statusCode,
|
||||||
|
errorCode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return createHttpError(`cloudtentacles ${label}失败:${message}`, {
|
||||||
|
statusCode: 500,
|
||||||
|
errorCode: 'cloudtentacles_full_flow_step_failed',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -74,6 +74,10 @@ export {}
|
|||||||
* @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesVirtualNumberInput} AdminCloudtentaclesVirtualNumberRouteBody
|
* @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesVirtualNumberInput} AdminCloudtentaclesVirtualNumberRouteBody
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesFullFlowInput} AdminCloudtentaclesFullFlowRouteBody
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigRouteBody
|
* @typedef {import('./admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigRouteBody
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -180,6 +180,18 @@ export {}
|
|||||||
* }} AdminCloudtentaclesVirtualNumberInput
|
* }} AdminCloudtentaclesVirtualNumberInput
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* baseUrl?: string
|
||||||
|
* token?: string
|
||||||
|
* deviceId?: string
|
||||||
|
* deviceType?: number | string
|
||||||
|
* skuId?: number | string
|
||||||
|
* skuCount?: number | string
|
||||||
|
* vnKey?: string
|
||||||
|
* }} AdminCloudtentaclesFullFlowInput
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* provider?: string
|
* provider?: string
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ http.interceptors.response.use(
|
|||||||
|
|
||||||
if (error?.config?.url && String(error.config.url).startsWith('/api/v1/admin')) {
|
if (error?.config?.url && String(error.config.url).startsWith('/api/v1/admin')) {
|
||||||
const status = Number(error?.response?.status || 0)
|
const status = Number(error?.response?.status || 0)
|
||||||
|
const errorCode = String(error?.response?.data?.errorCode || '').trim()
|
||||||
|
|
||||||
if (status === 401) {
|
if (status === 401 && shouldClearAdminSession(errorCode)) {
|
||||||
clearAdminSession()
|
clearAdminSession()
|
||||||
|
|
||||||
if (window.location.hash.startsWith('#/admin') && !window.location.hash.startsWith('#/admin/login')) {
|
if (window.location.hash.startsWith('#/admin') && !window.location.hash.startsWith('#/admin/login')) {
|
||||||
@@ -81,6 +82,15 @@ function resolveFallbackHttpMessage(error: unknown) {
|
|||||||
return '接口请求失败'
|
return '接口请求失败'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldClearAdminSession(errorCode: string) {
|
||||||
|
return [
|
||||||
|
'admin_auth_required',
|
||||||
|
'admin_auth_invalid',
|
||||||
|
'admin_auth_expired',
|
||||||
|
'admin_auth_user_invalid',
|
||||||
|
].includes(errorCode)
|
||||||
|
}
|
||||||
|
|
||||||
export function apiGet<T>(url: string, params?: Record<string, unknown>) {
|
export function apiGet<T>(url: string, params?: Record<string, unknown>) {
|
||||||
return http.get<ApiEnvelope<T>>(url, { params }) as unknown as Promise<ApiEnvelope<T>>
|
return http.get<ApiEnvelope<T>>(url, { params }) as unknown as Promise<ApiEnvelope<T>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,6 +311,18 @@ export function fetchAdminCloudtentaclesBindUrl(payload: {
|
|||||||
return apiPost<Record<string, unknown>>('/api/v1/admin/platform-config/cloudtentacles/vn/bind-url', payload)
|
return apiPost<Record<string, unknown>>('/api/v1/admin/platform-config/cloudtentacles/vn/bind-url', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function runAdminCloudtentaclesFullFlow(payload: {
|
||||||
|
baseUrl?: string
|
||||||
|
token?: string
|
||||||
|
deviceId?: string
|
||||||
|
deviceType?: number
|
||||||
|
skuId?: number
|
||||||
|
skuCount?: number
|
||||||
|
vnKey?: string
|
||||||
|
}) {
|
||||||
|
return apiPost<Record<string, unknown>>('/api/v1/admin/platform-config/cloudtentacles/debug/full-flow', payload)
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchAdminFulfillmentBindingConfigs() {
|
export function fetchAdminFulfillmentBindingConfigs() {
|
||||||
return apiGet<{
|
return apiGet<{
|
||||||
filePath: string
|
filePath: string
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
fetchAdminKhhaoSourceConfig,
|
fetchAdminKhhaoSourceConfig,
|
||||||
generateAdminCloudtentaclesVnLoginCode,
|
generateAdminCloudtentaclesVnLoginCode,
|
||||||
queryAdminKhhaoOrders,
|
queryAdminKhhaoOrders,
|
||||||
|
runAdminCloudtentaclesFullFlow,
|
||||||
saveAdminCloudtentaclesSourceConfig,
|
saveAdminCloudtentaclesSourceConfig,
|
||||||
saveAdminAgisoShopConfigs,
|
saveAdminAgisoShopConfigs,
|
||||||
saveAdminKhhaoSourceConfig,
|
saveAdminKhhaoSourceConfig,
|
||||||
@@ -108,7 +109,7 @@ const cloudtentaclesForm = ref({
|
|||||||
token: '',
|
token: '',
|
||||||
vnKey: '1',
|
vnKey: '1',
|
||||||
vnId: 0,
|
vnId: 0,
|
||||||
skuId: 0,
|
skuId: 5,
|
||||||
skuCount: 1,
|
skuCount: 1,
|
||||||
deviceId: '-',
|
deviceId: '-',
|
||||||
deviceType: 0,
|
deviceType: 0,
|
||||||
@@ -235,7 +236,7 @@ async function loadConfigs() {
|
|||||||
token: cloudtentaclesResponse.data.session?.token || '',
|
token: cloudtentaclesResponse.data.session?.token || '',
|
||||||
vnKey: '1',
|
vnKey: '1',
|
||||||
vnId: 0,
|
vnId: 0,
|
||||||
skuId: 0,
|
skuId: 5,
|
||||||
skuCount: 1,
|
skuCount: 1,
|
||||||
deviceId: cloudtentaclesResponse.data.source.deviceId || '-',
|
deviceId: cloudtentaclesResponse.data.source.deviceId || '-',
|
||||||
deviceType: cloudtentaclesResponse.data.source.deviceType || 0,
|
deviceType: cloudtentaclesResponse.data.source.deviceType || 0,
|
||||||
@@ -757,6 +758,16 @@ async function handleCloudtentaclesFetchBindUrl() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCloudtentaclesRunFullFlow() {
|
||||||
|
if (!ensureCloudtentaclesToken()) return
|
||||||
|
await runCloudtentaclesDebugAction('完整测试流程', () => runAdminCloudtentaclesFullFlow({
|
||||||
|
...buildCloudtentaclesTokenPayload(),
|
||||||
|
skuId: Number(cloudtentaclesForm.value.skuId || 0),
|
||||||
|
skuCount: Number(cloudtentaclesForm.value.skuCount || 1),
|
||||||
|
vnKey: cloudtentaclesForm.value.vnKey.trim(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(loadConfigs)
|
onMounted(loadConfigs)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -1385,6 +1396,7 @@ onMounted(loadConfigs)
|
|||||||
<el-button :loading="cloudtentaclesDebugLoading" round @click="handleCloudtentaclesFetchVnCode">获取验证码</el-button>
|
<el-button :loading="cloudtentaclesDebugLoading" round @click="handleCloudtentaclesFetchVnCode">获取验证码</el-button>
|
||||||
<el-button :loading="cloudtentaclesDebugLoading" round @click="handleCloudtentaclesVerifyVnCode">校验验证码</el-button>
|
<el-button :loading="cloudtentaclesDebugLoading" round @click="handleCloudtentaclesVerifyVnCode">校验验证码</el-button>
|
||||||
<el-button :loading="cloudtentaclesDebugLoading" round type="primary" @click="handleCloudtentaclesFetchBindUrl">兑换链接</el-button>
|
<el-button :loading="cloudtentaclesDebugLoading" round type="primary" @click="handleCloudtentaclesFetchBindUrl">兑换链接</el-button>
|
||||||
|
<el-button :loading="cloudtentaclesDebugLoading" round type="success" @click="handleCloudtentaclesRunFullFlow">完整流程</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user