From 063c3fd8a1b7b00e9238863a855f41848d854a68 Mon Sep 17 00:00:00 2001 From: yml Date: Sun, 3 May 2026 17:15:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E8=B4=A7=E4=B8=8E=E9=80=80=E8=BF=98?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/config/default.cjs | 2 + apps/backend/data/khhao-sync-state.json | 6 +-- .../src/routes/admin/platform-config.js | 21 +++++++++ .../admin/admin-platform-config-service.js | 24 ++++++++++ .../cloudtentacles/catalog-service.js | 44 +++++++++++++++++++ .../platforms/cloudtentacles/shared.js | 4 ++ .../cloudtentacles/virtual-number-service.js | 24 ++++++++++ apps/backend/src/types/admin-route-inputs.js | 4 ++ apps/backend/src/types/admin-write-inputs.js | 12 +++++ apps/backend/src/types/runtime-config.js | 2 + apps/frontend/src/services/admin.ts | 23 ++++++++++ .../views/admin/AdminPlatformShopsView.vue | 27 +++++++++++- 12 files changed, 188 insertions(+), 5 deletions(-) diff --git a/apps/backend/config/default.cjs b/apps/backend/config/default.cjs index 90707f0b..d47eadd2 100644 --- a/apps/backend/config/default.cjs +++ b/apps/backend/config/default.cjs @@ -87,6 +87,7 @@ module.exports = { categoriesPath: '/categories/get', skuListPath: '/sku/list', skuBuyPath: '/sku/buy', + skuUsePath: '/sku/use', knapsackPath: '/user/get_knapsack', vnListPath: '/vn/list', vnAppointPath: '/vn/appoint', @@ -94,6 +95,7 @@ module.exports = { vnVerifCodePath: '/public/vn_verif_code', vnVerifyLoginCodePath: '/vn/verify_login_code', vnBindUrlPath: '/vn/bind_url', + vnBackPath: '/vn/back', clientSource: 'ct-client', deviceId: '-', deviceType: 0, diff --git a/apps/backend/data/khhao-sync-state.json b/apps/backend/data/khhao-sync-state.json index f4d17625..06db5744 100644 --- a/apps/backend/data/khhao-sync-state.json +++ b/apps/backend/data/khhao-sync-state.json @@ -1,11 +1,11 @@ { "syncFromCreatedAt": "2026-05-02T10:53:28.550Z", - "lastRunStartedAt": "2026-05-03T08:34:17.211Z", - "lastRunFinishedAt": "2026-05-03T08:34:17.844Z", + "lastRunStartedAt": "2026-05-03T09:15:36.131Z", + "lastRunFinishedAt": "2026-05-03T09:15:37.160Z", "lastRunStatus": "success", "lastErrorMessage": "", "fetchedCount": 40, "syncedCount": 0, "ignoredCount": 40, - "lastOrderCreatedAt": "2026-05-03T08:33:37.000Z" + "lastOrderCreatedAt": "2026-05-03T09:15:32.000Z" } diff --git a/apps/backend/src/routes/admin/platform-config.js b/apps/backend/src/routes/admin/platform-config.js index 3835be7c..a936f233 100644 --- a/apps/backend/src/routes/admin/platform-config.js +++ b/apps/backend/src/routes/admin/platform-config.js @@ -4,6 +4,7 @@ import { Router } from 'express' import { appointAdminCloudtentaclesVirtualNumber, + backAdminCloudtentaclesVirtualNumber, buyAdminCloudtentaclesSku, fetchAdminCloudtentaclesVirtualNumberCode, generateAdminCloudtentaclesLoginCode, @@ -14,6 +15,7 @@ import { getAdminCloudtentaclesKnapsack, getAdminCloudtentaclesSourceConfig, getAdminCloudtentaclesSkuList, + useAdminCloudtentaclesSku, getAdminAgisoShopConfigs, getAdminFulfillmentBindingConfigs, getAdminKhhaoSourceConfig, @@ -54,6 +56,7 @@ import { createJsonHandler, requireAdminRoles } from './shared.js' /** @typedef {import('../../types/admin-route-inputs.js').AdminCloudtentaclesValidateSessionRouteBody} AdminCloudtentaclesValidateSessionRouteBody */ /** @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').AdminCloudtentaclesSkuUseRouteBody} AdminCloudtentaclesSkuUseRouteBody */ /** @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 */ @@ -429,6 +432,15 @@ router.post('/platform-config/cloudtentacles/sku/buy', createJsonHandler( }, )) +router.post('/platform-config/cloudtentacles/sku/use', createJsonHandler( + (req) => useAdminCloudtentaclesSku(/** @type {AdminCloudtentaclesSkuUseRouteBody} */ (req.body)), + { + successMessage: 'cloudtentacles 发货成功', + errorMessage: 'cloudtentacles 发货失败', + scope: '[admin/platform-config/cloudtentacles/sku/use]', + }, +)) + router.post('/platform-config/cloudtentacles/knapsack', createJsonHandler( (req) => getAdminCloudtentaclesKnapsack(/** @type {AdminCloudtentaclesCatalogQueryRouteBody} */ (req.body)), { @@ -492,6 +504,15 @@ router.post('/platform-config/cloudtentacles/vn/bind-url', createJsonHandler( }, )) +router.post('/platform-config/cloudtentacles/vn/back', createJsonHandler( + (req) => backAdminCloudtentaclesVirtualNumber(/** @type {AdminCloudtentaclesVirtualNumberRouteBody} */ (req.body)), + { + successMessage: 'cloudtentacles 号码退还成功', + errorMessage: 'cloudtentacles 号码退还失败', + scope: '[admin/platform-config/cloudtentacles/vn/back]', + }, +)) + router.post('/platform-config/cloudtentacles/debug/full-flow', createJsonHandler( (req) => runAdminCloudtentaclesFullFlow(/** @type {AdminCloudtentaclesFullFlowRouteBody} */ (req.body)), { diff --git a/apps/backend/src/services/admin/admin-platform-config-service.js b/apps/backend/src/services/admin/admin-platform-config-service.js index 288b15de..046db60d 100644 --- a/apps/backend/src/services/admin/admin-platform-config-service.js +++ b/apps/backend/src/services/admin/admin-platform-config-service.js @@ -49,10 +49,12 @@ import { getCloudtentaclesAsset, getCloudtentaclesCategories, listCloudtentaclesSku, + useCloudtentaclesSku, } from '../platforms/cloudtentacles/catalog-service.js' import { getCloudtentaclesKnapsack } from '../platforms/cloudtentacles/knapsack-service.js' import { appointCloudtentaclesVirtualNumber, + backCloudtentaclesVirtualNumber, fetchCloudtentaclesVirtualNumberCode, generateCloudtentaclesLoginCode, getCloudtentaclesBindUrl, @@ -87,6 +89,7 @@ import { resolveDisplayShopName } from './admin-read-shared-helpers.js' /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesValidateSessionInput} AdminCloudtentaclesValidateSessionInput */ /** @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').AdminCloudtentaclesSkuUseInput} AdminCloudtentaclesSkuUseInput */ /** @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 */ @@ -629,6 +632,17 @@ export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudt }) } +/** @param {AdminCloudtentaclesSkuUseInput} [payload] */ +export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuUseInput} */ ({})) { + const context = resolveCloudtentaclesAdminContext(payload) + return useCloudtentaclesSku({ + ...context, + id: payload.id, + virtualNumberId: payload.virtualNumberId, + phone: payload.phone, + }) +} + /** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ export async function getAdminCloudtentaclesKnapsack(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload) @@ -694,6 +708,16 @@ export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCl }) } +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveCloudtentaclesAdminContext(payload) + return backCloudtentaclesVirtualNumber({ + ...context, + key: payload.key, + id: payload.id, + }) +} + /** @param {AdminCloudtentaclesFullFlowInput} [payload] */ export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload) diff --git a/apps/backend/src/services/platforms/cloudtentacles/catalog-service.js b/apps/backend/src/services/platforms/cloudtentacles/catalog-service.js index f41971fb..f6fa2d1a 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/catalog-service.js +++ b/apps/backend/src/services/platforms/cloudtentacles/catalog-service.js @@ -96,6 +96,50 @@ export async function buyCloudtentaclesSku(payload = {}) { } } +export async function useCloudtentaclesSku(payload = {}) { + const token = requireToken(payload.token, 'cloudtentacles 发货缺少 token', 'cloudtentacles_sku_use_missing_token') + const skuId = requireId(payload.id, 'cloudtentacles 发货缺少商品 id', 'cloudtentacles_sku_use_missing_id') + const virtualNumberId = requireId( + payload.virtualNumberId, + 'cloudtentacles 发货缺少虚拟号 id', + 'cloudtentacles_sku_use_missing_virtual_number_id', + ) + const phone = String(payload.phone || '').trim() + if (!phone) { + throw createHttpError('cloudtentacles 发货缺少手机号', { + statusCode: 400, + errorCode: 'cloudtentacles_sku_use_missing_phone', + }) + } + + const config = resolveCloudtentaclesConfig(payload) + const result = await cloudtentaclesRequest(config.skuUsePath, { + ...config, + method: 'POST', + token, + body: { + id: skuId, + virtual_number_id: virtualNumberId, + phone, + }, + businessErrorStatusCode: 401, + businessErrorCode: 'cloudtentacles_sku_use_failed', + }) + + const responseData = isPlainObject(result.payload?.data) ? result.payload.data : {} + + return { + baseUrl: config.baseUrl, + id: skuId, + virtualNumberId, + phone, + sendType: Number(responseData.send_type || 0), + note: String(responseData.note || '').trim(), + responseMessage: String(result.payload?.message || result.payload?.msg || 'success'), + raw: result.payload ?? null, + } +} + function mapCategoryItem(item) { const source = isPlainObject(item) ? item : {} diff --git a/apps/backend/src/services/platforms/cloudtentacles/shared.js b/apps/backend/src/services/platforms/cloudtentacles/shared.js index e21c2f91..f7b64576 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/shared.js +++ b/apps/backend/src/services/platforms/cloudtentacles/shared.js @@ -17,6 +17,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) { categoriesPath: '/categories/get', skuListPath: '/sku/list', skuBuyPath: '/sku/buy', + skuUsePath: '/sku/use', knapsackPath: '/user/get_knapsack', vnListPath: '/vn/list', vnAppointPath: '/vn/appoint', @@ -24,6 +25,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) { vnVerifCodePath: '/public/vn_verif_code', vnVerifyLoginCodePath: '/vn/verify_login_code', vnBindUrlPath: '/vn/bind_url', + vnBackPath: '/vn/back', publicKeyPem: '', clientSource: 'ct-client', deviceId: '-', @@ -41,6 +43,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) { categoriesPath: normalizePath(overrides.categoriesPath || baseConfig.categoriesPath, '/categories/get'), skuListPath: normalizePath(overrides.skuListPath || baseConfig.skuListPath, '/sku/list'), skuBuyPath: normalizePath(overrides.skuBuyPath || baseConfig.skuBuyPath, '/sku/buy'), + skuUsePath: normalizePath(overrides.skuUsePath || baseConfig.skuUsePath, '/sku/use'), knapsackPath: normalizePath(overrides.knapsackPath || baseConfig.knapsackPath, '/user/get_knapsack'), vnListPath: normalizePath(overrides.vnListPath || baseConfig.vnListPath, '/vn/list'), vnAppointPath: normalizePath(overrides.vnAppointPath || baseConfig.vnAppointPath, '/vn/appoint'), @@ -54,6 +57,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) { '/vn/verify_login_code', ), vnBindUrlPath: normalizePath(overrides.vnBindUrlPath || baseConfig.vnBindUrlPath, '/vn/bind_url'), + vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'), publicKeyPem: normalizePem(overrides.publicKeyPem || baseConfig.publicKeyPem), clientSource: String(overrides.clientSource || baseConfig.clientSource || 'ct-client').trim() || 'ct-client', deviceId: String(overrides.deviceId ?? baseConfig.deviceId ?? '-').trim() || '-', diff --git a/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js b/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js index 8152e10a..6167baff 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js +++ b/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js @@ -159,6 +159,30 @@ export async function getCloudtentaclesBindUrl(payload = {}) { } } +export async function backCloudtentaclesVirtualNumber(payload = {}) { + const token = requireToken(payload.token, 'cloudtentacles 退还号码缺少 token', 'cloudtentacles_vn_back_missing_token') + const key = requireKey(payload.key, 'cloudtentacles 退还号码缺少 key', 'cloudtentacles_vn_back_missing_key') + const id = requireId(payload.id, 'cloudtentacles 退还号码缺少 id', 'cloudtentacles_vn_back_missing_id') + const config = resolveCloudtentaclesConfig(payload) + + const result = await cloudtentaclesRequest(config.vnBackPath, { + ...config, + method: 'POST', + token, + body: { key, id }, + businessErrorStatusCode: 401, + businessErrorCode: 'cloudtentacles_vn_back_failed', + }) + + return { + baseUrl: config.baseUrl, + key, + id, + responseMessage: String(result.payload?.message || result.payload?.msg || 'success'), + raw: result.payload ?? null, + } +} + function mapVirtualNumberItem(item) { const source = isPlainObject(item) ? item : {} const bindInfoText = String(source.bind_info || '').trim() diff --git a/apps/backend/src/types/admin-route-inputs.js b/apps/backend/src/types/admin-route-inputs.js index f3a39969..c3b89e9b 100644 --- a/apps/backend/src/types/admin-route-inputs.js +++ b/apps/backend/src/types/admin-route-inputs.js @@ -86,6 +86,10 @@ export {} * @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesSkuBuyInput} AdminCloudtentaclesSkuBuyRouteBody */ +/** + * @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesSkuUseInput} AdminCloudtentaclesSkuUseRouteBody + */ + /** * @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesVirtualNumberInput} AdminCloudtentaclesVirtualNumberRouteBody */ diff --git a/apps/backend/src/types/admin-write-inputs.js b/apps/backend/src/types/admin-write-inputs.js index 80a2a830..531da122 100644 --- a/apps/backend/src/types/admin-write-inputs.js +++ b/apps/backend/src/types/admin-write-inputs.js @@ -215,6 +215,18 @@ export {} * }} AdminCloudtentaclesSkuBuyInput */ +/** + * @typedef {{ + * baseUrl?: string + * token?: string + * deviceId?: string + * deviceType?: number | string + * id?: number | string + * virtualNumberId?: number | string + * phone?: string + * }} AdminCloudtentaclesSkuUseInput + */ + /** * @typedef {{ * baseUrl?: string diff --git a/apps/backend/src/types/runtime-config.js b/apps/backend/src/types/runtime-config.js index 38290841..9834cc5e 100644 --- a/apps/backend/src/types/runtime-config.js +++ b/apps/backend/src/types/runtime-config.js @@ -107,6 +107,7 @@ export {} * categoriesPath: string * skuListPath: string * skuBuyPath: string + * skuUsePath: string * knapsackPath: string * vnListPath: string * vnAppointPath: string @@ -114,6 +115,7 @@ export {} * vnVerifCodePath: string * vnVerifyLoginCodePath: string * vnBindUrlPath: string + * vnBackPath: string * publicKeyPem: string * clientSource: string * deviceId: string diff --git a/apps/frontend/src/services/admin.ts b/apps/frontend/src/services/admin.ts index 495396dc..38f75f86 100644 --- a/apps/frontend/src/services/admin.ts +++ b/apps/frontend/src/services/admin.ts @@ -285,6 +285,18 @@ export function buyAdminCloudtentaclesSku(payload: { return apiPost>('/api/v1/admin/platform-config/cloudtentacles/sku/buy', payload) } +export function useAdminCloudtentaclesSku(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + id?: number + virtualNumberId?: number + phone?: string +}) { + return apiPost>('/api/v1/admin/platform-config/cloudtentacles/sku/use', payload) +} + export function fetchAdminCloudtentaclesKnapsack(payload: { baseUrl?: string token?: string @@ -359,6 +371,17 @@ export function fetchAdminCloudtentaclesBindUrl(payload: { return apiPost>('/api/v1/admin/platform-config/cloudtentacles/vn/bind-url', payload) } +export function backAdminCloudtentaclesVn(payload: { + baseUrl?: string + token?: string + deviceId?: string + deviceType?: number + key?: string + id?: number +}) { + return apiPost>('/api/v1/admin/platform-config/cloudtentacles/vn/back', payload) +} + export function runAdminCloudtentaclesFullFlow(payload: { baseUrl?: string token?: string diff --git a/apps/frontend/src/views/admin/AdminPlatformShopsView.vue b/apps/frontend/src/views/admin/AdminPlatformShopsView.vue index d75f349f..52184b34 100644 --- a/apps/frontend/src/views/admin/AdminPlatformShopsView.vue +++ b/apps/frontend/src/views/admin/AdminPlatformShopsView.vue @@ -4,6 +4,7 @@ import { computed, onMounted, ref } from 'vue' import { showError, showSuccess } from '@/lib/feedback' import { appointAdminCloudtentaclesVn, + backAdminCloudtentaclesVn, buyAdminCloudtentaclesSku, consumeAdminKuaishouEticket, fetchAdminCloudtentaclesAsset, @@ -30,6 +31,7 @@ import { syncAdminKhhaoOrders, testAdminCloudtentaclesLogin, testAdminKhhaoLogin, + useAdminCloudtentaclesSku, validateAdminCloudtentaclesSession, verifyAdminCloudtentaclesVnCode, } from '@/services/admin' @@ -1023,6 +1025,16 @@ async function handleCloudtentaclesBuySku() { })) } +async function handleCloudtentaclesUseSku() { + if (!ensureCloudtentaclesToken()) return + await runCloudtentaclesDebugAction('执行发货', () => useAdminCloudtentaclesSku({ + ...buildCloudtentaclesTokenPayload(), + id: Number(cloudtentaclesForm.value.skuId || 0), + virtualNumberId: Number(cloudtentaclesForm.value.vnId || 0), + phone: cloudtentaclesForm.value.phone.trim(), + })) +} + async function handleCloudtentaclesFetchKnapsack() { if (!ensureCloudtentaclesToken()) return await runCloudtentaclesDebugAction('查询背包', () => fetchAdminCloudtentaclesKnapsack(buildCloudtentaclesTokenPayload())) @@ -1098,6 +1110,15 @@ async function handleCloudtentaclesFetchBindUrl() { })) } +async function handleCloudtentaclesBackVn() { + if (!ensureCloudtentaclesToken()) return + await runCloudtentaclesDebugAction('退还号码', () => backAdminCloudtentaclesVn({ + ...buildCloudtentaclesTokenPayload(), + key: cloudtentaclesForm.value.vnKey.trim(), + id: Number(cloudtentaclesForm.value.vnId || 0), + })) +} + async function handleCloudtentaclesRunFullFlow() { if (!ensureCloudtentaclesToken()) return await runCloudtentaclesDebugAction('完整测试流程', () => runAdminCloudtentaclesFullFlow({ @@ -2025,20 +2046,22 @@ onMounted(loadConfigs)

cloudtentacles 开发调试工具

-

仅开发模式展示。用于调试分类、SKU、购买、背包、虚拟号和兑换链接链路。

+

仅开发模式展示。用于调试分类、SKU、购买、发货、虚拟号、兑换链接和结束退还链路。

余额 分类 SKU 购买 + 发货 背包 虚拟号列表 申请虚拟号 生成登录码 获取验证码 校验验证码 - 兑换链接 + 兑换链接 + 退还号码 完整流程