From 83cb2ff1fadce973c0210f1f64492dee08bea850 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 14 May 2026 13:08:24 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=BA=86=20khhao=20=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/config/default.cjs | 7 - apps/backend/data/khhao-sources.json | 13 - apps/backend/data/khhao-sync-state.json | 13 - .../data/kuaishou-cloud-fulfillment.json | 29 +- .../data/order-fulfillment-bindings.json | 2 - apps/backend/src/config/runtime.js | 25 -- apps/backend/src/index.js | 8 - .../src/routes/admin/platform-config.js | 113 ----- .../services/admin/platform-config/domain.js | 14 - .../admin/platform-config/domain.test.js | 24 -- .../fulfillment-bindings-service.js | 3 - .../admin/platform-config/khhao-service.js | 76 ---- .../services/admin/platform-config/khhao.js | 83 ---- .../admin/platform-config/khhao.test.js | 166 -------- .../services/admin/platform-config/mappers.js | 44 +- .../admin/platform-config/mappers.test.js | 78 +--- .../services/admin/platform-config/service.js | 8 - .../admin/platform-config/validation.js | 66 +-- .../admin/platform-config/validation.test.js | 39 +- .../services/admin/platform-config/writes.js | 16 - .../admin/platform-config/writes.test.js | 32 -- .../fulfillment-bootstrap-service.js | 11 +- .../fulfillment-bootstrap-service.test.js | 9 +- .../fulfillment-binding-config-service.js | 4 +- ...fulfillment-binding-config-service.test.js | 11 +- ...aishou-cloud-fulfillment-config-service.js | 6 +- ...u-cloud-fulfillment-config-service.test.js | 12 +- .../platforms/khhao/auto-sync-service.js | 227 ---------- .../platforms/khhao/auto-sync-service.test.js | 79 ---- .../platforms/khhao/captcha-service.js | 75 ---- .../platforms/khhao/order-mapper-service.js | 169 -------- .../platforms/khhao/order-query-service.js | 112 ----- .../platforms/khhao/order-sync-service.js | 150 ------- .../platforms/khhao/session-service.js | 216 ---------- .../src/services/platforms/khhao/shared.js | 113 ----- .../platforms/khhao/source-config-service.js | 80 ---- .../platforms/khhao/sync-state-service.js | 129 ------ apps/backend/src/types/admin-route-inputs.js | 12 - apps/backend/src/types/admin-write-inputs.js | 39 -- apps/backend/src/types/runtime-config.js | 7 - apps/frontend/src/components.d.ts | 1 - .../admin/AdminPlatformAgisoSection.vue | 2 +- .../admin/AdminPlatformKhhaoSection.vue | 264 ------------ .../composables/admin/platform-shops/types.ts | 2 +- .../platform-shops/useAdminKhhaoPlatform.ts | 215 ---------- .../src/services/admin/platform-config.ts | 55 --- apps/frontend/src/types/admin.ts | 109 ----- .../admin/AdminFulfillmentBindingsView.vue | 205 ++------- .../AdminKuaishouCloudFulfillmentView.vue | 301 +------------ .../views/admin/AdminPlatformShopsView.vue | 74 +--- docs/91卡券/6.开发设计.md | 4 +- docs/app信息.md | 3 +- docs/cloudflared_tunnel.txt | 4 + docs/cloudtentacles发货平台接入设计.md | 28 +- docs/khhao来源接入设计.md | 403 ------------------ 前后端现状分析.md | 11 +- 56 files changed, 108 insertions(+), 3893 deletions(-) delete mode 100644 apps/backend/data/khhao-sources.json delete mode 100644 apps/backend/data/khhao-sync-state.json delete mode 100644 apps/backend/src/services/admin/platform-config/khhao-service.js delete mode 100644 apps/backend/src/services/admin/platform-config/khhao.js delete mode 100644 apps/backend/src/services/admin/platform-config/khhao.test.js delete mode 100644 apps/backend/src/services/platforms/khhao/auto-sync-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/auto-sync-service.test.js delete mode 100644 apps/backend/src/services/platforms/khhao/captcha-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/order-mapper-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/order-query-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/order-sync-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/session-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/shared.js delete mode 100644 apps/backend/src/services/platforms/khhao/source-config-service.js delete mode 100644 apps/backend/src/services/platforms/khhao/sync-state-service.js delete mode 100644 apps/frontend/src/components/admin/AdminPlatformKhhaoSection.vue delete mode 100644 apps/frontend/src/composables/admin/platform-shops/useAdminKhhaoPlatform.ts delete mode 100644 docs/khhao来源接入设计.md diff --git a/apps/backend/config/default.cjs b/apps/backend/config/default.cjs index 83406bf5..6093efe8 100644 --- a/apps/backend/config/default.cjs +++ b/apps/backend/config/default.cjs @@ -78,13 +78,6 @@ module.exports = { timestampToleranceSeconds: 600, cardsEncoding: 'aes-256-ecb-base64', }, - khhao: { - baseUrl: 'https://admin.khhao.com', - timeoutMs: 5000, - loginPath: '/c/login/index.php', - captchaPath: '/verify_img.php', - orderListPath: '/c/payOrder/get.php', - }, cloudtentacles: { baseUrl: 'https://123.207.217.176', timeoutMs: 5000, diff --git a/apps/backend/data/khhao-sources.json b/apps/backend/data/khhao-sources.json deleted file mode 100644 index cde28bcf..00000000 --- a/apps/backend/data/khhao-sources.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "enabled": true, - "baseUrl": "https://admin.khhao.com", - "username": "order_check", - "password": "order_check_test1", - "maxCaptchaAttempts": 3, - "autoSync": { - "enabled": false, - "intervalMinutes": 3, - "pages": 2, - "pageSize": 20 - } -} diff --git a/apps/backend/data/khhao-sync-state.json b/apps/backend/data/khhao-sync-state.json deleted file mode 100644 index b0190558..00000000 --- a/apps/backend/data/khhao-sync-state.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "syncFromCreatedAt": "2026-05-02T10:53:28.550Z", - "lastRunStartedAt": "2026-05-11T14:09:54.989Z", - "lastRunFinishedAt": "2026-05-11T14:09:56.241Z", - "lastRunStatus": "success", - "lastErrorMessage": "", - "fetchedCount": 40, - "syncedCount": 0, - "ignoredCount": 40, - "lastOrderCreatedAt": "2026-05-11T14:09:50.000Z", - "watchMode": "normal", - "watchOrders": [] -} diff --git a/apps/backend/data/kuaishou-cloud-fulfillment.json b/apps/backend/data/kuaishou-cloud-fulfillment.json index 9cb6b09e..92fd5d03 100644 --- a/apps/backend/data/kuaishou-cloud-fulfillment.json +++ b/apps/backend/data/kuaishou-cloud-fulfillment.json @@ -2,13 +2,12 @@ "enabled": true, "items": [ { - "id": "khhao 订单取样导入", + "id": "91卡券订单取样导入", "enabled": true, "priority": 100, "provider": "91kaquan", "platform": "kuaishou", "shopId": "91kaquan", - "khhaoShopId": "", "internalSkuCode": "套装-浪漫天命", "internalSkuName": "套装-浪漫天命", "externalSkuCode": "183999074510936", @@ -26,32 +25,6 @@ "kuaishouConsumeShopId": "3676797936", "kuaishouConsumeShopName": "小鱼儿游戏服务店", "notes": "从 91卡券订单 2613300010204561 导入" - }, - { - "id": "套装-浪漫天命", - "enabled": true, - "priority": 100, - "provider": "khhao", - "platform": "kuaishou", - "shopId": "4269276762", - "khhaoShopId": "10", - "internalSkuCode": "套装-浪漫天命", - "internalSkuName": "套装-浪漫天命", - "externalSkuCode": "952", - "externalItemId": "952", - "externalSkuName": "测试2", - "resolvedSkuName": "测试2", - "cloudSourceKey": "default", - "cloudSkuId": 28, - "cloudSkuName": "套装-浪漫天命", - "vnKey": "1", - "autoBuyEnabled": true, - "minAssetReserve": 0, - "autoReturnNumberAfterDispatch": true, - "autoConsumeAfterDispatch": true, - "kuaishouConsumeShopId": "4269276762", - "kuaishouConsumeShopName": "稚嫩游戏交易店", - "notes": "从 khhao 订单 2612400002308936 导入" } ] } diff --git a/apps/backend/data/order-fulfillment-bindings.json b/apps/backend/data/order-fulfillment-bindings.json index 6fffc22f..c3837b32 100644 --- a/apps/backend/data/order-fulfillment-bindings.json +++ b/apps/backend/data/order-fulfillment-bindings.json @@ -4,7 +4,6 @@ "platform": "xianyu", "shopId": "2209880145223", "shopName": "", - "khhaoShopId": "", "skuCode": "海底捞第五人格皮肤", "skuName": "海底捞第五人格皮肤", "profileKey": "tencent_claim_redeem", @@ -25,7 +24,6 @@ "platform": "xianyu", "shopId": "2209880145223", "shopName": "", - "khhaoShopId": "", "skuCode": "三角洲海底捞动作", "skuName": "海底捞干员庆生动作1个", "profileKey": "tencent_claim_redeem", diff --git a/apps/backend/src/config/runtime.js b/apps/backend/src/config/runtime.js index 6933e396..65db233d 100644 --- a/apps/backend/src/config/runtime.js +++ b/apps/backend/src/config/runtime.js @@ -270,31 +270,6 @@ function applyEnvOverrides(baseConfig) { nextConfig.platforms.ninetyone.cardsEncoding = ninetyoneCardsEncoding } - const khhaoBaseUrl = String(process.env.KHHAO_BASE_URL || '').trim() - if (khhaoBaseUrl) { - nextConfig.platforms.khhao.baseUrl = khhaoBaseUrl - } - - const khhaoTimeoutMs = parseInteger(process.env.KHHAO_TIMEOUT_MS) - if (khhaoTimeoutMs !== null) { - nextConfig.platforms.khhao.timeoutMs = khhaoTimeoutMs - } - - const khhaoLoginPath = String(process.env.KHHAO_LOGIN_PATH || '').trim() - if (khhaoLoginPath) { - nextConfig.platforms.khhao.loginPath = khhaoLoginPath - } - - const khhaoCaptchaPath = String(process.env.KHHAO_CAPTCHA_PATH || '').trim() - if (khhaoCaptchaPath) { - nextConfig.platforms.khhao.captchaPath = khhaoCaptchaPath - } - - const khhaoOrderListPath = String(process.env.KHHAO_ORDER_LIST_PATH || '').trim() - if (khhaoOrderListPath) { - nextConfig.platforms.khhao.orderListPath = khhaoOrderListPath - } - const cloudtentaclesBaseUrl = String(process.env.CLOUDTENTACLES_BASE_URL || '').trim() if (cloudtentaclesBaseUrl) { nextConfig.platforms.cloudtentacles.baseUrl = cloudtentaclesBaseUrl diff --git a/apps/backend/src/index.js b/apps/backend/src/index.js index a33daa28..f0b8c52a 100644 --- a/apps/backend/src/index.js +++ b/apps/backend/src/index.js @@ -9,7 +9,6 @@ import open91Router from './routes/open-91.js' import webhooksRouter from './routes/webhooks.js' import { ensureAdminUsersBootstrapped } from './services/admin/admin-auth-service.js' import { ensureFulfillmentCatalogBootstrapped } from './services/bootstrap/fulfillment-bootstrap-service.js' -import { startKhhaoAutoSyncLoop, stopKhhaoAutoSyncLoop } from './services/platforms/khhao/auto-sync-service.js' import { closeLocalOcrWorker, warmupLocalOcrWorker } from './services/session/ocr.js' import { closeAllTencentBrowserSessions, warmupTencentBrowser } from './services/session/session.js' import { buildSuccessPayload } from './utils/http.js' @@ -163,7 +162,6 @@ async function bootstrapCoreServices() { attempt: startupState.core.attemptCount, }) - startKhhaoAutoSyncLoop() void bootstrapBrowser() void bootstrapOcr() break @@ -372,12 +370,6 @@ async function shutdown(signal) { logError('[shutdown]', 'failed to close local OCR worker', error) } - try { - stopKhhaoAutoSyncLoop() - } catch (error) { - logError('[shutdown]', 'failed to stop khhao auto sync loop', error) - } - if (!server.listening) { return } diff --git a/apps/backend/src/routes/admin/platform-config.js b/apps/backend/src/routes/admin/platform-config.js index dcd9f544..b92be0b3 100644 --- a/apps/backend/src/routes/admin/platform-config.js +++ b/apps/backend/src/routes/admin/platform-config.js @@ -19,7 +19,6 @@ import { useAdminCloudtentaclesSku, getAdminAgisoShopConfigs, getAdminFulfillmentBindingConfigs, - getAdminKhhaoSourceConfig, getAdminNinetyoneOrders, consumeAdminKuaishouEticket, failAdminNinetyoneOrder, @@ -27,18 +26,14 @@ import { lookupAdminFulfillmentBindingOrder, queryAdminKuaishouEticketShopInfo, queryAdminKuaishouEticketDetail, - queryAdminKhhaoOrders, retryAdminNinetyoneOrder, runAdminCloudtentaclesFullFlow, sendAdminCloudtentaclesSmsCode, - syncAdminKhhaoOrders, testAdminCloudtentaclesLogin, - testAdminKhhaoLogin, updateAdminKuaishouCloudFulfillmentConfig, updateAdminKuaishouEticketSourceConfig, updateAdminCloudtentaclesSourceConfig, updateAdminAgisoShopConfigs, - updateAdminKhhaoSourceConfig, updateAdminFulfillmentBindingConfigs, verifyAdminCloudtentaclesLoginCode, validateAdminCloudtentaclesSession, @@ -48,9 +43,6 @@ import { createJsonHandler, requireAdminRoles } from './shared.js' /** @typedef {import('../../types/admin-route-inputs.js').AdminAgisoShopConfigRouteBody} AdminAgisoShopConfigRouteBody */ /** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingConfigRouteBody} AdminFulfillmentBindingConfigRouteBody */ /** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingLookupRouteBody} AdminFulfillmentBindingLookupRouteBody */ -/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoOrderQueryRouteBody} AdminKhhaoOrderQueryRouteBody */ -/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoSourceConfigRouteBody} AdminKhhaoSourceConfigRouteBody */ -/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoTestLoginRouteBody} AdminKhhaoTestLoginRouteBody */ /** @typedef {import('../../types/admin-route-inputs.js').AdminEntityRouteParams} AdminEntityRouteParams */ /** @typedef {import('../../types/admin-route-inputs.js').AdminKuaishouEticketSourceConfigRouteBody} AdminKuaishouEticketSourceConfigRouteBody */ /** @typedef {import('../../types/admin-route-inputs.js').AdminKuaishouEticketDetailQueryRouteBody} AdminKuaishouEticketDetailQueryRouteBody */ @@ -133,111 +125,6 @@ router.post('/platform-config/kuaishou-eticket-source', createJsonHandler( }, )) -router.get('/platform-config/khhao-source', createJsonHandler( - () => getAdminKhhaoSourceConfig(), - { - successMessage: 'ok', - errorMessage: '读取 khhao 来源配置失败', - scope: '[admin/platform-config/khhao-source]', - }, -)) - -router.post('/platform-config/khhao-source', createJsonHandler( - (req) => updateAdminKhhaoSourceConfig(/** @type {AdminKhhaoSourceConfigRouteBody} */ (req.body)), - { - successMessage: 'khhao 来源配置已保存', - errorMessage: '保存 khhao 来源配置失败', - scope: '[admin/platform-config/khhao-source]', - audit: (_req, data) => { - const result = /** @type {{ filePath?: string, source?: { username?: string, enabled?: boolean } }} */ (data) - return { - action: 'platform_khhao_source_updated', - targetType: 'platform_config', - targetId: 'khhao_source', - data: { - filePath: String(result.filePath || '').trim(), - username: String(result.source?.username || '').trim(), - enabled: Boolean(result.source?.enabled), - }, - } - }, - }, -)) - -router.post('/platform-config/khhao/test-login', createJsonHandler( - (req) => testAdminKhhaoLogin(/** @type {AdminKhhaoTestLoginRouteBody} */ (req.body)), - { - successMessage: 'khhao 登录测试成功', - errorMessage: 'khhao 登录测试失败', - scope: '[admin/platform-config/khhao/test-login]', - audit: (req, data) => { - const body = /** @type {AdminKhhaoTestLoginRouteBody} */ (req.body) - const result = /** @type {{ baseUrl?: string, username?: string, attempt?: number, session?: { cookieCount?: number } }} */ (data) - return { - action: 'platform_khhao_test_login', - targetType: 'platform_config', - targetId: String(result.username || body.username || '').trim() || 'khhao', - data: { - baseUrl: result.baseUrl || String(body.baseUrl || '').trim(), - attempt: Number(result.attempt || 0), - cookieCount: Number(result.session?.cookieCount || 0), - }, - } - }, - }, -)) - -router.post('/platform-config/khhao/query-orders', createJsonHandler( - (req) => queryAdminKhhaoOrders(/** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body)), - { - successMessage: 'khhao 订单查询成功', - errorMessage: 'khhao 订单查询失败', - scope: '[admin/platform-config/khhao/query-orders]', - audit: (req, data) => { - const body = /** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body) - const result = /** @type {{ page?: number, limit?: number, itemCount?: number, total?: number, baseUrl?: string }} */ (data) - return { - action: 'platform_khhao_query_orders', - targetType: 'platform_config', - targetId: String(body.username || '').trim() || 'khhao', - data: { - baseUrl: result.baseUrl || String(body.baseUrl || '').trim(), - page: Number(result.page || 1), - limit: Number(result.limit || 50), - itemCount: Number(result.itemCount || 0), - total: Number(result.total || 0), - }, - } - }, - }, -)) - -router.post('/platform-config/khhao/sync-orders', createJsonHandler( - (req) => syncAdminKhhaoOrders(/** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body)), - { - successMessage: 'khhao 订单同步成功', - errorMessage: 'khhao 订单同步失败', - scope: '[admin/platform-config/khhao/sync-orders]', - audit: (req, data) => { - const body = /** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body) - const result = /** @type {{ page?: number, limit?: number, fetchedCount?: number, syncedCount?: number, ignoredCount?: number, baseUrl?: string }} */ (data) - return { - action: 'platform_khhao_sync_orders', - targetType: 'platform_config', - targetId: String(body.username || '').trim() || 'khhao', - data: { - baseUrl: result.baseUrl || String(body.baseUrl || '').trim(), - page: Number(result.page || 1), - limit: Number(result.limit || 50), - fetchedCount: Number(result.fetchedCount || 0), - syncedCount: Number(result.syncedCount || 0), - ignoredCount: Number(result.ignoredCount || 0), - }, - } - }, - }, -)) - router.get('/platform-config/ninetyone/orders', createJsonHandler( (req) => getAdminNinetyoneOrders(req.query), { diff --git a/apps/backend/src/services/admin/platform-config/domain.js b/apps/backend/src/services/admin/platform-config/domain.js index f3a38fb1..ce3f7bb0 100644 --- a/apps/backend/src/services/admin/platform-config/domain.js +++ b/apps/backend/src/services/admin/platform-config/domain.js @@ -50,7 +50,6 @@ export function mapAdminFulfillmentBindingConfigItem(item) { platform: String(item?.platform || '').trim(), shopId: String(item?.shopId || '').trim(), shopName: String(item?.shopName || '').trim(), - khhaoShopId: String(item?.khhaoShopId || '').trim(), skuCode: String(item?.skuCode || '').trim(), skuName: String(item?.skuName || '').trim(), profileKey: String(item?.profileKey || '').trim(), @@ -66,19 +65,6 @@ export function mapAdminFulfillmentBindingConfigItem(item) { } } -export function resolveFulfillmentBindingMatchShopId({ - provider = '', - platform = '', - shopId = '', - khhaoShopId = '', -} = {}) { - if (String(provider || '').trim() === 'khhao' && String(platform || '').trim() === 'kuaishou') { - return String(khhaoShopId || shopId || '').trim() - } - - return String(shopId || '').trim() -} - export function matchesObservedProduct(binding, observed) { const provider = String(binding?.provider || '').trim() const platform = String(binding?.platform || '').trim() diff --git a/apps/backend/src/services/admin/platform-config/domain.test.js b/apps/backend/src/services/admin/platform-config/domain.test.js index 4a1dbc44..3f69a6a6 100644 --- a/apps/backend/src/services/admin/platform-config/domain.test.js +++ b/apps/backend/src/services/admin/platform-config/domain.test.js @@ -9,7 +9,6 @@ import { mapAdminFulfillmentBindingConfigItem, mapAdminObservedProductItem, matchesObservedProduct, - resolveFulfillmentBindingMatchShopId, } from './domain.js' test('mapAdminAgisoMessagingDefaults normalizes escaped newlines', () => { @@ -78,7 +77,6 @@ test('mapAdminFulfillmentBindingConfigItem normalizes binding shape', () => { platform: 'xianyu', shopId: 'shop-1', shopName: '', - khhaoShopId: '', skuCode: 'sku-1', skuName: '', profileKey: '', @@ -95,28 +93,6 @@ test('mapAdminFulfillmentBindingConfigItem normalizes binding shape', () => { ) }) -test('resolveFulfillmentBindingMatchShopId prefers khhaoShopId for khhao kuaishou bindings', () => { - assert.equal( - resolveFulfillmentBindingMatchShopId({ - provider: 'khhao', - platform: 'kuaishou', - shopId: 'shop-a', - khhaoShopId: 'khhao-1', - }), - 'khhao-1', - ) - - assert.equal( - resolveFulfillmentBindingMatchShopId({ - provider: 'agiso', - platform: 'xianyu', - shopId: 'shop-a', - khhaoShopId: 'khhao-1', - }), - 'shop-a', - ) -}) - test('matchesObservedProduct honors provider platform shop and external fields', () => { const binding = { provider: 'agiso', diff --git a/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js b/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js index edb1064f..42c33f42 100644 --- a/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js +++ b/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js @@ -8,7 +8,6 @@ import { saveOrderFulfillmentBindingConfigs, } from '../../order/fulfillment-binding-config-service.js' import { enrichAgisoXianyuTradeOrder } from '../../platforms/agiso/xianyu/order-detail-service.js' -import { getKuaishouEticketSourceConfig, resolveKuaishouEticketShopConfig } from '../../platforms/kuaishou-eticket/source-config-service.js' import { syncConfiguredFulfillmentBindings } from '../../bootstrap/fulfillment-bootstrap-service.js' import { getFulfillmentProfileByKey } from '../../../repositories/fulfillment-profile-repo.js' import { createHttpError } from '../../../utils/http.js' @@ -78,8 +77,6 @@ export async function updateAdminFulfillmentBindingConfigs( ) { const bindingsInput = Array.isArray(payload.bindings) ? payload.bindings : [] const normalizedBindings = await validateAdminFulfillmentBindingConfigs(bindingsInput, { - kuaishouEticketSource: getKuaishouEticketSourceConfig(), - resolveKuaishouEticketShopConfig, getFulfillmentProfileByKey, }) assertLegacyBindingConfigsOnly(normalizedBindings) diff --git a/apps/backend/src/services/admin/platform-config/khhao-service.js b/apps/backend/src/services/admin/platform-config/khhao-service.js deleted file mode 100644 index de708750..00000000 --- a/apps/backend/src/services/admin/platform-config/khhao-service.js +++ /dev/null @@ -1,76 +0,0 @@ -// @ts-check - -import { queryKhhaoOrderList } from '../../platforms/khhao/order-query-service.js' -import { syncKhhaoOrders } from '../../platforms/khhao/order-sync-service.js' -import { getKhhaoSourceConfig, getKhhaoSourcesFilePath, saveKhhaoSourceConfig } from '../../platforms/khhao/source-config-service.js' -import { ensureKhhaoSyncBaseline, getKhhaoSyncState, getKhhaoSyncStateFilePath } from '../../platforms/khhao/sync-state-service.js' -import { loginKhhaoSession } from '../../platforms/khhao/session-service.js' -import { - buildAdminKhhaoLoginPayload, - buildAdminKhhaoLoginResult, - buildAdminKhhaoOrderQueryResult, - buildAdminKhhaoSyncPayload, - resolveAdminKhhaoCredentialPayload, -} from './khhao.js' -import { normalizeAdminKhhaoSourceConfigPayload } from './writes.js' -import { - mapAdminKhhaoSourceConfig, - mapAdminKhhaoSyncState, -} from './mappers.js' - -/** @typedef {import('../../../types/admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigInput */ -/** @typedef {import('../../../types/admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryInput */ -/** @typedef {import('../../../types/admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginInput */ - -export function getAdminKhhaoSourceConfig() { - const config = getKhhaoSourceConfig() - const syncState = getKhhaoSyncState() - - return { - filePath: getKhhaoSourcesFilePath(), - stateFilePath: getKhhaoSyncStateFilePath(), - source: mapAdminKhhaoSourceConfig(config), - syncState: mapAdminKhhaoSyncState(syncState), - } -} - -/** @param {AdminKhhaoSourceConfigInput} [payload] */ -export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSourceConfigInput} */ ({})) { - const saved = saveKhhaoSourceConfig(normalizeAdminKhhaoSourceConfigPayload(payload)) - const syncState = saved.autoSync?.enabled ? ensureKhhaoSyncBaseline() : getKhhaoSyncState() - - return { - filePath: getKhhaoSourcesFilePath(), - stateFilePath: getKhhaoSyncStateFilePath(), - source: mapAdminKhhaoSourceConfig(saved), - syncState: mapAdminKhhaoSyncState(syncState), - } -} - -/** @param {AdminKhhaoTestLoginInput} [payload] */ -export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLoginInput} */ ({})) { - const session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload)) - return buildAdminKhhaoLoginResult(session) -} - -/** @param {AdminKhhaoOrderQueryInput} [payload] */ -export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { - const credential = resolveAdminKhhaoCredentialPayload(payload) - const session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload, { - requestIdScope: 'query-orders', - })) - - const result = await queryKhhaoOrderList({ - baseUrl: credential.baseUrl, - page: payload.page, - limit: payload.limit, - session, - }) - - return buildAdminKhhaoOrderQueryResult(session, result) -} - -/** @param {AdminKhhaoOrderQueryInput} [payload] */ -export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { - return syncKhhaoOrders(buildAdminKhhaoSyncPayload(payload)) -} diff --git a/apps/backend/src/services/admin/platform-config/khhao.js b/apps/backend/src/services/admin/platform-config/khhao.js deleted file mode 100644 index a4a4e69b..00000000 --- a/apps/backend/src/services/admin/platform-config/khhao.js +++ /dev/null @@ -1,83 +0,0 @@ -// @ts-check - -import { getKhhaoSourceConfig } from '../../platforms/khhao/source-config-service.js' -import { mapKhhaoOrderPreviewList } from '../../platforms/khhao/order-mapper-service.js' -import { maskSecret } from './mappers.js' - -export function resolveAdminKhhaoCredentialPayload( - payload = {}, - options = {}, -) { - const savedSource = /** @type {Record} */ ( - options.savedSource || getKhhaoSourceConfig() - ) - - return { - baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), - username: String(payload.username || savedSource.username || '').trim(), - password: String(payload.password || savedSource.password || '').trim(), - maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, - } -} - -export function buildAdminKhhaoLoginPayload( - payload = {}, - options = {}, -) { - const credential = resolveAdminKhhaoCredentialPayload(payload, options) - const requestIdScope = String(options.requestIdScope || 'test-login').trim() || 'test-login' - - return { - ...credential, - includeImageBase64: payload.includeImageBase64, - requestId: `admin-khhao-${requestIdScope}:${credential.username || 'anonymous'}`, - } -} - -export function buildAdminKhhaoSyncPayload( - payload = {}, - options = {}, -) { - return { - ...payload, - ...resolveAdminKhhaoCredentialPayload(payload, options), - } -} - -export function buildAdminKhhaoLoginResult(session = {}) { - const cookieKeys = Object.keys(session.cookieMap || {}) - - return { - baseUrl: String(session.baseUrl || '').trim(), - username: String(session.username || '').trim(), - loggedInAt: String(session.loggedInAt || '').trim(), - attempt: Number(session.attempt || 0), - responseMessage: String(session.responseMessage || '').trim(), - captcha: { - recognizedText: String(session.captchaText || '').trim(), - imageBase64: String(session.captchaImageBase64 || '').trim(), - }, - session: { - cookieKeys, - cookieCount: cookieKeys.length, - cookieHeaderMasked: maskSecret(session.cookieHeader), - }, - } -} - -export function buildAdminKhhaoOrderQueryResult( - session = {}, - result = {}, -) { - const items = Array.isArray(result.items) ? result.items : [] - - return { - baseUrl: String(session.baseUrl || '').trim(), - page: Number(result.page || 1), - limit: Number(result.limit || 0), - total: Number(result.total || 0), - itemCount: items.length, - items: mapKhhaoOrderPreviewList(items), - rawItems: items, - } -} diff --git a/apps/backend/src/services/admin/platform-config/khhao.test.js b/apps/backend/src/services/admin/platform-config/khhao.test.js deleted file mode 100644 index 9a65fc0a..00000000 --- a/apps/backend/src/services/admin/platform-config/khhao.test.js +++ /dev/null @@ -1,166 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' - -import { - buildAdminKhhaoLoginPayload, - buildAdminKhhaoLoginResult, - buildAdminKhhaoOrderQueryResult, - buildAdminKhhaoSyncPayload, - resolveAdminKhhaoCredentialPayload, -} from './khhao.js' - -test('resolveAdminKhhaoCredentialPayload merges payload and saved source defaults', () => { - assert.deepEqual( - resolveAdminKhhaoCredentialPayload( - { - baseUrl: ' https://override.example.com ', - maxCaptchaAttempts: 5, - }, - { - savedSource: { - baseUrl: 'https://saved.example.com', - username: 'saved-user', - password: 'saved-pass', - maxCaptchaAttempts: 3, - }, - }, - ), - { - baseUrl: 'https://override.example.com', - username: 'saved-user', - password: 'saved-pass', - maxCaptchaAttempts: 5, - }, - ) -}) - -test('buildAdminKhhaoLoginPayload appends request id and includeImageBase64', () => { - assert.deepEqual( - buildAdminKhhaoLoginPayload( - { - username: ' user ', - includeImageBase64: true, - }, - { - requestIdScope: 'query-orders', - savedSource: { - baseUrl: 'https://saved.example.com', - password: 'saved-pass', - maxCaptchaAttempts: 3, - }, - }, - ), - { - baseUrl: 'https://saved.example.com', - username: 'user', - password: 'saved-pass', - maxCaptchaAttempts: 3, - includeImageBase64: true, - requestId: 'admin-khhao-query-orders:user', - }, - ) -}) - -test('buildAdminKhhaoSyncPayload merges normalized credential fields into payload', () => { - assert.deepEqual( - buildAdminKhhaoSyncPayload( - { - page: 2, - limit: 20, - username: ' user ', - }, - { - savedSource: { - baseUrl: 'https://saved.example.com', - password: 'saved-pass', - maxCaptchaAttempts: 4, - }, - }, - ), - { - page: 2, - limit: 20, - username: 'user', - baseUrl: 'https://saved.example.com', - password: 'saved-pass', - maxCaptchaAttempts: 4, - }, - ) -}) - -test('buildAdminKhhaoLoginResult assembles login response payload', () => { - assert.deepEqual( - buildAdminKhhaoLoginResult({ - baseUrl: ' https://admin.khhao.com ', - username: ' test-user ', - loggedInAt: ' 2026-05-04T12:00:00.000Z ', - attempt: 2, - responseMessage: ' success ', - captchaText: ' abcd ', - captchaImageBase64: ' image-data ', - cookieMap: { - PHPSESSID: 'a', - foo: 'b', - }, - cookieHeader: 'abcdef1234567890', - }), - { - baseUrl: 'https://admin.khhao.com', - username: 'test-user', - loggedInAt: '2026-05-04T12:00:00.000Z', - attempt: 2, - responseMessage: 'success', - captcha: { - recognizedText: 'abcd', - imageBase64: 'image-data', - }, - session: { - cookieKeys: ['PHPSESSID', 'foo'], - cookieCount: 2, - cookieHeaderMasked: 'abcdef****567890', - }, - }, - ) -}) - -test('buildAdminKhhaoOrderQueryResult maps preview list and preserves raw items', () => { - const rawItems = [ - { - ordersn: ' A1 ', - pingtai: '3', - pingtaiName: '快手', - shopid: '1001', - shopName: '店铺A', - goodid: 'g1', - goodName: '商品A', - sku: 'sku-1', - num: '2', - fee: '12.34', - status: '2', - statusName: '已支付', - addtime: '2026-05-04 10:00:00', - }, - ] - - const result = buildAdminKhhaoOrderQueryResult( - { - baseUrl: ' https://admin.khhao.com ', - }, - { - page: 2, - limit: 20, - total: 50, - items: rawItems, - }, - ) - - assert.equal(result.baseUrl, 'https://admin.khhao.com') - assert.equal(result.page, 2) - assert.equal(result.limit, 20) - assert.equal(result.total, 50) - assert.equal(result.itemCount, 1) - assert.equal(result.rawItems, rawItems) - assert.equal(result.items[0].platformOrderId, 'A1') - assert.equal(result.items[0].platform, 'kuaishou') - assert.equal(result.items[0].itemTitle, '商品A') -}) diff --git a/apps/backend/src/services/admin/platform-config/mappers.js b/apps/backend/src/services/admin/platform-config/mappers.js index ec2c879c..dd7bedfb 100644 --- a/apps/backend/src/services/admin/platform-config/mappers.js +++ b/apps/backend/src/services/admin/platform-config/mappers.js @@ -26,47 +26,6 @@ export function maskPhone(value) { return `${normalized.slice(0, 3)}****${normalized.slice(-4)}` } -export function mapAdminKhhaoSourceConfig(config = {}) { - return { - enabled: config.enabled !== false, - baseUrl: String(config.baseUrl || '').trim(), - username: String(config.username || '').trim(), - password: String(config.password || '').trim(), - maxCaptchaAttempts: Number(config.maxCaptchaAttempts || 3) || 3, - autoSync: { - enabled: config.autoSync?.enabled === true, - intervalMinutes: Number(config.autoSync?.intervalMinutes || 3) || 3, - pages: Number(config.autoSync?.pages || 2) || 2, - pageSize: Number(config.autoSync?.pageSize || 20) || 20, - }, - } -} - -export function mapAdminKhhaoSyncState(syncState = {}) { - return { - syncFromCreatedAt: String(syncState.syncFromCreatedAt || '').trim(), - lastRunStartedAt: String(syncState.lastRunStartedAt || '').trim(), - lastRunFinishedAt: String(syncState.lastRunFinishedAt || '').trim(), - lastRunStatus: String(syncState.lastRunStatus || '').trim(), - lastErrorMessage: String(syncState.lastErrorMessage || '').trim(), - fetchedCount: Number(syncState.fetchedCount || 0), - syncedCount: Number(syncState.syncedCount || 0), - ignoredCount: Number(syncState.ignoredCount || 0), - watchMode: String(syncState.watchMode || 'normal').trim() || 'normal', - watchOrders: Array.isArray(syncState.watchOrders) - ? syncState.watchOrders.map((item) => ({ - platformOrderId: String(item?.platformOrderId || '').trim(), - shopId: String(item?.shopId || '').trim(), - shopName: String(item?.shopName || '').trim(), - itemTitle: String(item?.itemTitle || '').trim(), - payStatus: String(item?.payStatus || '').trim(), - detectedAt: String(item?.detectedAt || '').trim(), - lastSeenAt: String(item?.lastSeenAt || '').trim(), - })) - : [], - } -} - export function mapAdminKuaishouEticketShopItem(item = {}) { return { shopId: String(item.shopId || '').trim(), @@ -118,10 +77,9 @@ export function mapAdminKuaishouCloudFulfillmentItem(item = {}) { id: String(item.id || '').trim(), enabled: item.enabled !== false, priority: Number(item.priority || 100) || 100, - provider: String(item.provider || 'khhao').trim() || 'khhao', + provider: String(item.provider || '91kaquan').trim() || '91kaquan', platform: String(item.platform || 'kuaishou').trim() || 'kuaishou', shopId: String(item.shopId || '').trim(), - khhaoShopId: String(item.khhaoShopId || '').trim(), internalSkuCode: String(item.internalSkuCode || '').trim(), internalSkuName: String(item.internalSkuName || '').trim(), externalSkuCode: String(item.externalSkuCode || '').trim(), diff --git a/apps/backend/src/services/admin/platform-config/mappers.test.js b/apps/backend/src/services/admin/platform-config/mappers.test.js index 07c64cbc..d6aa08b8 100644 --- a/apps/backend/src/services/admin/platform-config/mappers.test.js +++ b/apps/backend/src/services/admin/platform-config/mappers.test.js @@ -3,8 +3,6 @@ import assert from 'node:assert/strict' import { mapAdminCloudtentaclesSession, - mapAdminKhhaoSourceConfig, - mapAdminKhhaoSyncState, mapAdminKuaishouCloudFulfillmentSource, mapAdminKuaishouEticketSourceConfig, maskPhone, @@ -23,79 +21,6 @@ test('maskPhone hides middle digits for mobile numbers', () => { assert.equal(maskPhone(''), '') }) -test('mapAdminKhhaoSourceConfig normalizes defaults and nested auto sync values', () => { - assert.deepEqual( - mapAdminKhhaoSourceConfig({ - enabled: true, - baseUrl: ' https://admin.khhao.com ', - username: ' user ', - password: ' pass ', - maxCaptchaAttempts: '0', - autoSync: { - enabled: true, - intervalMinutes: '', - pages: '0', - pageSize: '50', - }, - }), - { - enabled: true, - baseUrl: 'https://admin.khhao.com', - username: 'user', - password: 'pass', - maxCaptchaAttempts: 3, - autoSync: { - enabled: true, - intervalMinutes: 3, - pages: 2, - pageSize: 50, - }, - }, - ) -}) - -test('mapAdminKhhaoSyncState normalizes watch orders and fallback values', () => { - assert.deepEqual( - mapAdminKhhaoSyncState({ - watchMode: '', - fetchedCount: '8', - watchOrders: [ - { - platformOrderId: ' A1 ', - shopId: ' S1 ', - shopName: ' 店铺 ', - itemTitle: ' 商品 ', - payStatus: ' unpaid ', - detectedAt: ' t1 ', - lastSeenAt: ' t2 ', - }, - ], - }), - { - syncFromCreatedAt: '', - lastRunStartedAt: '', - lastRunFinishedAt: '', - lastRunStatus: '', - lastErrorMessage: '', - fetchedCount: 8, - syncedCount: 0, - ignoredCount: 0, - watchMode: 'normal', - watchOrders: [ - { - platformOrderId: 'A1', - shopId: 'S1', - shopName: '店铺', - itemTitle: '商品', - payStatus: 'unpaid', - detectedAt: 't1', - lastSeenAt: 't2', - }, - ], - }, - ) -}) - test('mapAdminKuaishouEticketSourceConfig maps shops and masks cookies', () => { assert.deepEqual( mapAdminKuaishouEticketSourceConfig( @@ -169,10 +94,9 @@ test('mapAdminKuaishouCloudFulfillmentSource normalizes items and defaults', () id: 'item-1', enabled: true, priority: 100, - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', shopId: '', - khhaoShopId: '', internalSkuCode: '', internalSkuName: '', externalSkuCode: '', diff --git a/apps/backend/src/services/admin/platform-config/service.js b/apps/backend/src/services/admin/platform-config/service.js index 4ab03dd3..65dc474b 100644 --- a/apps/backend/src/services/admin/platform-config/service.js +++ b/apps/backend/src/services/admin/platform-config/service.js @@ -3,14 +3,6 @@ export { updateAdminAgisoShopConfigs, } from './agiso-service.js' -export { - getAdminKhhaoSourceConfig, - updateAdminKhhaoSourceConfig, - testAdminKhhaoLogin, - queryAdminKhhaoOrders, - syncAdminKhhaoOrders, -} from './khhao-service.js' - export { getAdminNinetyoneOrders, retryAdminNinetyoneOrder, diff --git a/apps/backend/src/services/admin/platform-config/validation.js b/apps/backend/src/services/admin/platform-config/validation.js index 27365c54..aa495db3 100644 --- a/apps/backend/src/services/admin/platform-config/validation.js +++ b/apps/backend/src/services/admin/platform-config/validation.js @@ -3,7 +3,6 @@ import { createHttpError } from '../../../utils/http.js' import { normalizeProductName } from '../../order/product-match-service.js' import { isPlainObject } from './context.js' -import { resolveFulfillmentBindingMatchShopId } from './domain.js' export function assertAdminFulfillmentBindingsInput(bindings) { if (Array.isArray(bindings)) { @@ -21,10 +20,6 @@ export function normalizeAdminFulfillmentBindingItem( options = {}, ) { const index = Number(options.index || 0) - const kuaishouEticketSource = /** @type {Record} */ (options.kuaishouEticketSource || {}) - const resolveKuaishouEticketShopConfig = /** @type {(shop: Record, source: Record) => Record | null} */ ( - options.resolveKuaishouEticketShopConfig || (() => null) - ) if (!isPlainObject(rawBinding)) { throw createHttpError(`第 ${index + 1} 条规则格式不正确`, { statusCode: 400, @@ -34,9 +29,8 @@ export function normalizeAdminFulfillmentBindingItem( const provider = String(rawBinding.provider || 'agiso').trim() || 'agiso' const platform = String(rawBinding.platform || '').trim() - let shopId = String(rawBinding.shopId || '').trim() - let shopName = String(rawBinding.shopName || '').trim() - const khhaoShopId = String(rawBinding.khhaoShopId || '').trim() + const shopId = String(rawBinding.shopId || '').trim() + const shopName = String(rawBinding.shopName || '').trim() const skuCode = String(rawBinding.skuCode || '').trim() const skuName = String(rawBinding.skuName || '').trim() const profileKey = String(rawBinding.profileKey || '').trim() || 'manual_review' @@ -46,44 +40,6 @@ export function normalizeAdminFulfillmentBindingItem( const externalSkuName = String(match.externalSkuName || '').trim() const config = isPlainObject(rawBinding.config) ? { ...rawBinding.config } : {} - if (provider === 'khhao' && platform === 'kuaishou') { - if (!khhaoShopId) { - throw createHttpError(`第 ${index + 1} 条快手规则缺少 khhao 店铺 ID`, { - statusCode: 400, - errorCode: 'admin_fulfillment_bindings_missing_khhao_shop_id', - }) - } - - const matchedShop = resolveKuaishouEticketShopConfig( - { - shopId, - shopName, - }, - kuaishouEticketSource, - ) - const matchedShopId = String(matchedShop?.shopId || '').trim() - const matchedShopName = String(matchedShop?.kshopName || '').trim() - - if (!matchedShopId || !matchedShopName) { - throw createHttpError( - `第 ${index + 1} 条快手规则的店铺未匹配到已配置的快手官方店铺,请先到“平台配置”补齐 Cookie 后再选择`, - { - statusCode: 400, - errorCode: 'admin_fulfillment_bindings_kuaishou_shop_not_configured', - }, - ) - } - - shopId = matchedShopId - shopName = matchedShopName - config.kuaishouShop = { - ...(isPlainObject(config.kuaishouShop) ? config.kuaishouShop : {}), - shopId, - shopName, - khhaoShopId, - } - } - if (!skuCode) { throw createHttpError(`第 ${index + 1} 条规则缺少内部履约 SKU`, { statusCode: 400, @@ -103,7 +59,6 @@ export function normalizeAdminFulfillmentBindingItem( platform, shopId, shopName, - khhaoShopId, skuCode, skuName, profileKey, @@ -123,12 +78,7 @@ export function buildAdminFulfillmentBindingUniqueKey(binding) { return [ String(binding?.provider || '').trim(), String(binding?.platform || '').trim(), - resolveFulfillmentBindingMatchShopId({ - provider: binding?.provider, - platform: binding?.platform, - shopId: binding?.shopId, - khhaoShopId: binding?.khhaoShopId, - }), + String(binding?.shopId || '').trim(), String(binding?.match?.externalItemId || '').trim(), String(binding?.match?.externalSkuCode || '').trim(), normalizeProductName(String(binding?.match?.externalSkuName || '').trim()), @@ -142,10 +92,6 @@ export async function validateAdminFulfillmentBindingConfigs( ) { assertAdminFulfillmentBindingsInput(bindings) - const kuaishouEticketSource = /** @type {Record} */ (options.kuaishouEticketSource || {}) - const resolveKuaishouEticketShopConfig = /** @type {(shop: Record, source: Record) => Record | null} */ ( - options.resolveKuaishouEticketShopConfig || (() => null) - ) const getFulfillmentProfileByKey = /** @type {(profileKey: string) => Promise} */ ( options.getFulfillmentProfileByKey || (async () => null) ) @@ -153,11 +99,7 @@ export async function validateAdminFulfillmentBindingConfigs( const normalizedBindings = [] for (const [index, rawBinding] of bindings.entries()) { - const normalized = normalizeAdminFulfillmentBindingItem(rawBinding, { - index, - kuaishouEticketSource, - resolveKuaishouEticketShopConfig, - }) + const normalized = normalizeAdminFulfillmentBindingItem(rawBinding, { index }) const profile = await getFulfillmentProfileByKey(normalized.profileKey) if (!profile) { diff --git a/apps/backend/src/services/admin/platform-config/validation.test.js b/apps/backend/src/services/admin/platform-config/validation.test.js index 2434d0ba..9be59f24 100644 --- a/apps/backend/src/services/admin/platform-config/validation.test.js +++ b/apps/backend/src/services/admin/platform-config/validation.test.js @@ -13,14 +13,13 @@ test('assertAdminFulfillmentBindingsInput rejects non-array payloads', () => { assert.throws(() => assertAdminFulfillmentBindingsInput({}), /履约配置格式不正确/) }) -test('normalizeAdminFulfillmentBindingItem normalizes kuaishou binding shop mapping', () => { +test('normalizeAdminFulfillmentBindingItem normalizes generic binding fields', () => { const result = normalizeAdminFulfillmentBindingItem( { - provider: 'khhao', - platform: 'kuaishou', - shopId: 'legacy-shop', - shopName: '旧店铺', - khhaoShopId: 'khhao-1', + provider: ' agiso ', + platform: ' xianyu ', + shopId: ' shop-1 ', + shopName: ' 店铺 ', skuCode: 'sku-1', match: { externalSkuCode: 'ext-1', @@ -31,24 +30,14 @@ test('normalizeAdminFulfillmentBindingItem normalizes kuaishou binding shop mapp }, }, }, - { - index: 0, - kuaishouEticketSource: { source: true }, - resolveKuaishouEticketShopConfig() { - return { - shopId: 'ks-100', - kshopName: '快手店铺A', - } - }, - }, + { index: 0 }, ) assert.deepEqual(result, { - provider: 'khhao', - platform: 'kuaishou', - shopId: 'ks-100', - shopName: '快手店铺A', - khhaoShopId: 'khhao-1', + provider: 'agiso', + platform: 'xianyu', + shopId: 'shop-1', + shopName: '店铺', skuCode: 'sku-1', skuName: '', profileKey: 'manual_review', @@ -57,9 +46,6 @@ test('normalizeAdminFulfillmentBindingItem normalizes kuaishou binding shop mapp config: { kuaishouShop: { note: 'keep', - shopId: 'ks-100', - shopName: '快手店铺A', - khhaoShopId: 'khhao-1', }, }, match: { @@ -104,10 +90,9 @@ test('normalizeAdminFulfillmentBindingItem rejects missing sku and match conditi test('buildAdminFulfillmentBindingUniqueKey uses normalized external sku name and shop id rules', () => { assert.equal( buildAdminFulfillmentBindingUniqueKey({ - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', shopId: 'shop-a', - khhaoShopId: 'khhao-a', skuCode: 'sku-1', match: { externalItemId: '', @@ -115,7 +100,7 @@ test('buildAdminFulfillmentBindingUniqueKey uses normalized external sku name an externalSkuName: ' 礼包 A ', }, }), - 'khhao::kuaishou::khhao-a::::::礼包 a::sku-1', + '91kaquan::kuaishou::shop-a::::::礼包 a::sku-1', ) }) diff --git a/apps/backend/src/services/admin/platform-config/writes.js b/apps/backend/src/services/admin/platform-config/writes.js index 5cfe0f75..ded6a60f 100644 --- a/apps/backend/src/services/admin/platform-config/writes.js +++ b/apps/backend/src/services/admin/platform-config/writes.js @@ -88,22 +88,6 @@ export function buildAdminAgisoMessagingSavePayload( } } -export function normalizeAdminKhhaoSourceConfigPayload(payload = {}) { - return { - enabled: payload.enabled !== false, - baseUrl: String(payload.baseUrl || '').trim() || 'https://admin.khhao.com', - username: String(payload.username || '').trim(), - password: String(payload.password || '').trim(), - maxCaptchaAttempts: Number(payload.maxCaptchaAttempts || 3) || 3, - autoSync: { - enabled: payload.autoSync?.enabled === true, - intervalMinutes: Number(payload.autoSync?.intervalMinutes || 3) || 3, - pages: Number(payload.autoSync?.pages || 2) || 2, - pageSize: Number(payload.autoSync?.pageSize || 20) || 20, - }, - } -} - export function normalizeAdminCloudtentaclesSourceConfigPayload(payload = {}) { return { enabled: payload.enabled !== false, diff --git a/apps/backend/src/services/admin/platform-config/writes.test.js b/apps/backend/src/services/admin/platform-config/writes.test.js index 056fb0bf..2a7f2548 100644 --- a/apps/backend/src/services/admin/platform-config/writes.test.js +++ b/apps/backend/src/services/admin/platform-config/writes.test.js @@ -5,7 +5,6 @@ import { buildAdminAgisoMessagingSavePayload, buildAdminAgisoShopConfigUpdate, normalizeAdminCloudtentaclesSourceConfigPayload, - normalizeAdminKhhaoSourceConfigPayload, } from './writes.js' test('buildAdminAgisoShopConfigUpdate merges item fields and drops explicit empty templates', () => { @@ -102,37 +101,6 @@ test('buildAdminAgisoMessagingSavePayload updates defaults and keeps only shops ) }) -test('normalizeAdminKhhaoSourceConfigPayload applies defaults and numeric fallbacks', () => { - assert.deepEqual( - normalizeAdminKhhaoSourceConfigPayload({ - enabled: true, - baseUrl: ' https://admin.khhao.com ', - username: ' user ', - password: ' pass ', - maxCaptchaAttempts: 0, - autoSync: { - enabled: true, - intervalMinutes: '', - pages: '0', - pageSize: '50', - }, - }), - { - enabled: true, - baseUrl: 'https://admin.khhao.com', - username: 'user', - password: 'pass', - maxCaptchaAttempts: 3, - autoSync: { - enabled: true, - intervalMinutes: 3, - pages: 2, - pageSize: 50, - }, - }, - ) -}) - test('normalizeAdminCloudtentaclesSourceConfigPayload applies defaults', () => { assert.deepEqual( normalizeAdminCloudtentaclesSourceConfigPayload({ diff --git a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js index 393e656c..857872db 100644 --- a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js +++ b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.js @@ -165,26 +165,17 @@ export async function syncConfiguredFulfillmentBindings(profileMap = {}) { } export function resolveBindingMatchShopIds(binding = {}) { - if (String(binding.provider || '').trim() === 'khhao' && String(binding.platform || '').trim() === 'kuaishou') { - const candidates = [...new Set([ - String(binding.khhaoShopId || '').trim(), - String(binding.shopId || '').trim(), - ].filter(Boolean))] - return candidates.length > 0 ? candidates : [''] - } - return [String(binding.shopId || '').trim()] } function resolveBindingRuntimeConfig(binding = {}) { const baseConfig = isPlainObject(binding.config) ? { ...binding.config } : {} - if (String(binding.provider || '').trim() === 'khhao' && String(binding.platform || '').trim() === 'kuaishou') { + if (String(binding.provider || '').trim() === '91kaquan' && String(binding.platform || '').trim() === 'kuaishou') { baseConfig.kuaishouShop = { ...(isPlainObject(baseConfig.kuaishouShop) ? baseConfig.kuaishouShop : {}), shopId: String(binding.shopId || '').trim(), shopName: String(binding.shopName || '').trim(), - khhaoShopId: String(binding.khhaoShopId || '').trim(), } } diff --git a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js index 6616d81e..0fb21e92 100644 --- a/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js +++ b/apps/backend/src/services/bootstrap/fulfillment-bootstrap-service.test.js @@ -3,15 +3,14 @@ import assert from 'node:assert/strict' import { resolveBindingMatchShopIds } from './fulfillment-bootstrap-service.js' -test('resolveBindingMatchShopIds returns dual shop ids for khhao kuaishou bindings', () => { +test('resolveBindingMatchShopIds returns configured shop id for 91kaquan kuaishou bindings', () => { assert.deepEqual( resolveBindingMatchShopIds({ - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', - shopId: '4269276762', - khhaoShopId: '10', + shopId: '91kaquan', }), - ['10', '4269276762'], + ['91kaquan'], ) }) diff --git a/apps/backend/src/services/order/fulfillment-binding-config-service.js b/apps/backend/src/services/order/fulfillment-binding-config-service.js index d12f08cd..b21a93ab 100644 --- a/apps/backend/src/services/order/fulfillment-binding-config-service.js +++ b/apps/backend/src/services/order/fulfillment-binding-config-service.js @@ -57,7 +57,7 @@ export function filterLegacyOrderFulfillmentBindings(bindings = []) { } export function isKuaishouCloudFulfillmentBinding(binding = {}) { - return String(binding?.provider || '').trim() === 'khhao' + return String(binding?.provider || '').trim() === '91kaquan' && String(binding?.platform || '').trim() === 'kuaishou' } @@ -70,7 +70,6 @@ function normalizeOrderFulfillmentBinding(rawValue) { const platform = String(rawValue.platform || '').trim() const shopId = String(rawValue.shopId || '').trim() const shopName = String(rawValue.shopName || '').trim() - const khhaoShopId = String(rawValue.khhaoShopId || '').trim() const skuCode = String(rawValue.skuCode || '').trim() const skuName = String(rawValue.skuName || '').trim() const profileKey = String(rawValue.profileKey || '').trim() || 'manual_review' @@ -88,7 +87,6 @@ function normalizeOrderFulfillmentBinding(rawValue) { platform, shopId, shopName, - khhaoShopId, skuCode, skuName, profileKey, diff --git a/apps/backend/src/services/order/fulfillment-binding-config-service.test.js b/apps/backend/src/services/order/fulfillment-binding-config-service.test.js index 60df4603..a886995c 100644 --- a/apps/backend/src/services/order/fulfillment-binding-config-service.test.js +++ b/apps/backend/src/services/order/fulfillment-binding-config-service.test.js @@ -6,10 +6,10 @@ import { isKuaishouCloudFulfillmentBinding, } from './fulfillment-binding-config-service.js' -test('isKuaishouCloudFulfillmentBinding only marks khhao kuaishou rules as cloud rules', () => { +test('isKuaishouCloudFulfillmentBinding only marks 91kaquan kuaishou rules as cloud rules', () => { assert.equal( isKuaishouCloudFulfillmentBinding({ - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', }), true, @@ -24,7 +24,7 @@ test('isKuaishouCloudFulfillmentBinding only marks khhao kuaishou rules as cloud ) }) -test('filterLegacyOrderFulfillmentBindings keeps agiso rules and removes khhao kuaishou rules', () => { +test('filterLegacyOrderFulfillmentBindings keeps agiso rules and removes 91kaquan kuaishou rules', () => { assert.deepEqual( filterLegacyOrderFulfillmentBindings([ { @@ -34,10 +34,9 @@ test('filterLegacyOrderFulfillmentBindings keeps agiso rules and removes khhao k skuCode: 'sku-a', }, { - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', - shopId: '4269276762', - khhaoShopId: '10', + shopId: '91kaquan', skuCode: 'sku-b', }, ]), diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js index 52efd9c0..b629a01e 100644 --- a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js +++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js @@ -30,10 +30,9 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config = {}) { return items .filter((item) => item && item.enabled !== false) .map((item) => ({ - provider: String(item.provider || 'khhao').trim() || 'khhao', + provider: String(item.provider || '91kaquan').trim() || '91kaquan', platform: String(item.platform || 'kuaishou').trim() || 'kuaishou', shopId: String(item.shopId || '').trim(), - khhaoShopId: String(item.khhaoShopId || '').trim(), skuCode: String(item.internalSkuCode || '').trim(), skuName: String(item.internalSkuName || '').trim(), profileKey: 'kuaishou_ct_assisted', @@ -121,10 +120,9 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) { id: String(rawValue.id || internalSkuCode).trim() || internalSkuCode, enabled: rawValue.enabled !== false, priority: normalizePriority(rawValue.priority), - provider: String(rawValue.provider || 'khhao').trim() || 'khhao', + provider: String(rawValue.provider || '91kaquan').trim() || '91kaquan', platform: String(rawValue.platform || 'kuaishou').trim() || 'kuaishou', shopId: String(rawValue.shopId || '').trim(), - khhaoShopId: String(rawValue.khhaoShopId || '').trim(), internalSkuCode, internalSkuName: String(rawValue.internalSkuName || '').trim() || internalSkuCode, externalSkuCode, diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js index 36054c2f..dcee312c 100644 --- a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js +++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.test.js @@ -3,16 +3,15 @@ import assert from 'node:assert/strict' import { mapKuaishouCloudFulfillmentItemsToBindings } from './kuaishou-cloud-fulfillment-config-service.js' -test('mapKuaishouCloudFulfillmentItemsToBindings preserves khhao shop id', () => { +test('mapKuaishouCloudFulfillmentItemsToBindings maps 91kaquan rules', () => { assert.deepEqual( mapKuaishouCloudFulfillmentItemsToBindings({ items: [ { enabled: true, - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', - shopId: '4269276762', - khhaoShopId: '10', + shopId: '91kaquan', internalSkuCode: 'sku-1', internalSkuName: '内部商品', externalSkuCode: '952', @@ -24,10 +23,9 @@ test('mapKuaishouCloudFulfillmentItemsToBindings preserves khhao shop id', () => }), [ { - provider: 'khhao', + provider: '91kaquan', platform: 'kuaishou', - shopId: '4269276762', - khhaoShopId: '10', + shopId: '91kaquan', skuCode: 'sku-1', skuName: '内部商品', profileKey: 'kuaishou_ct_assisted', diff --git a/apps/backend/src/services/platforms/khhao/auto-sync-service.js b/apps/backend/src/services/platforms/khhao/auto-sync-service.js deleted file mode 100644 index 35fab853..00000000 --- a/apps/backend/src/services/platforms/khhao/auto-sync-service.js +++ /dev/null @@ -1,227 +0,0 @@ -// @ts-check - -import { getKhhaoSourceConfig } from './source-config-service.js' -import { ensureKhhaoSyncBaseline, getKhhaoSyncState, saveKhhaoSyncState } from './sync-state-service.js' -import { syncKhhaoOrders } from './order-sync-service.js' -import { logError, logInfo, logWarn } from '../../../utils/logger.js' -import { nowIso } from '../../../utils/time.js' - -let timer = null -let running = false -const BOOST_INTERVAL_MIN_MS = 3_000 -const BOOST_INTERVAL_MAX_MS = 5_000 - -export function startKhhaoAutoSyncLoop() { - scheduleNextRun(5_000) -} - -export function stopKhhaoAutoSyncLoop() { - if (timer) { - clearTimeout(timer) - timer = null - } -} - -async function runKhhaoAutoSyncCycle() { - const source = getKhhaoSourceConfig() - const currentState = getKhhaoSyncState() - - if (!source.enabled || !source.autoSync?.enabled) { - scheduleNextRun(resolveIntervalMs(source.autoSync?.intervalMinutes)) - return - } - - if (running) { - logWarn('[khhao/auto-sync]', '检测到上一次自动同步仍在运行,本轮跳过') - scheduleNextRun(resolveNextDelayMs(source, currentState)) - return - } - - if (!String(source.username || '').trim() || !String(source.password || '').trim()) { - logWarn('[khhao/auto-sync]', 'khhao 自动同步已启用,但缺少账号或密码') - scheduleNextRun(resolveIntervalMs(source.autoSync?.intervalMinutes)) - return - } - - running = true - const baselineState = ensureKhhaoSyncBaseline() - const startedAt = nowIso() - - saveKhhaoSyncState({ - ...baselineState, - lastRunStartedAt: startedAt, - lastRunStatus: 'running', - lastErrorMessage: '', - }) - - try { - const result = await syncKhhaoOrders({ - baseUrl: source.baseUrl, - username: source.username, - password: source.password, - maxCaptchaAttempts: source.maxCaptchaAttempts, - pages: source.autoSync.pages, - limit: source.autoSync.pageSize, - createdAfter: baselineState.syncFromCreatedAt, - }) - - const latestCreatedAt = resolveLatestOrderCreatedAt(result.results) - const nextWatchOrders = reconcileKhhaoWatchOrders(baselineState.watchOrders, result.results, startedAt) - const watchMode = nextWatchOrders.length > 0 ? 'boosted' : 'normal' - - saveKhhaoSyncState({ - ...getKhhaoSyncState(), - syncFromCreatedAt: baselineState.syncFromCreatedAt, - lastRunStartedAt: startedAt, - lastRunFinishedAt: nowIso(), - lastRunStatus: 'success', - lastErrorMessage: '', - fetchedCount: result.fetchedCount, - syncedCount: result.syncedCount, - ignoredCount: result.ignoredCount, - lastOrderCreatedAt: latestCreatedAt, - watchMode, - watchOrders: nextWatchOrders, - }) - - logInfo('[khhao/auto-sync]', 'khhao 自动同步完成', { - fetchedCount: result.fetchedCount, - syncedCount: result.syncedCount, - ignoredCount: result.ignoredCount, - syncFromCreatedAt: baselineState.syncFromCreatedAt, - watchMode, - watchOrderCount: nextWatchOrders.length, - watchOrders: nextWatchOrders.map((item) => ({ - platformOrderId: item.platformOrderId, - shopName: item.shopName, - payStatus: item.payStatus, - })), - }) - } catch (error) { - const message = error instanceof Error ? error.message : String(error || 'khhao 自动同步失败') - saveKhhaoSyncState({ - ...getKhhaoSyncState(), - syncFromCreatedAt: baselineState.syncFromCreatedAt, - lastRunStartedAt: startedAt, - lastRunFinishedAt: nowIso(), - lastRunStatus: 'failed', - lastErrorMessage: message, - }) - logError('[khhao/auto-sync]', 'khhao 自动同步失败', error) - } finally { - running = false - scheduleNextRun(resolveNextDelayMs(source, getKhhaoSyncState())) - } -} - -function scheduleNextRun(delayMs) { - stopKhhaoAutoSyncLoop() - timer = setTimeout(() => { - void runKhhaoAutoSyncCycle() - }, delayMs) -} - -function resolveIntervalMs(value) { - const minutes = Number(value) - const normalized = Number.isInteger(minutes) && minutes > 0 ? minutes : 3 - return normalized * 60 * 1000 -} - -function resolveNextDelayMs(source, syncState) { - if (hasActiveKhhaoWatchOrders(syncState?.watchOrders)) { - return resolveBoostIntervalMs() - } - - return resolveIntervalMs(source?.autoSync?.intervalMinutes) -} - -function resolveBoostIntervalMs() { - const delta = BOOST_INTERVAL_MAX_MS - BOOST_INTERVAL_MIN_MS - return BOOST_INTERVAL_MIN_MS + Math.floor(Math.random() * (delta + 1)) -} - -export function reconcileKhhaoWatchOrders(currentWatchOrders = [], results = [], now = nowIso()) { - const nextMap = new Map(normalizeKhhaoWatchOrders(currentWatchOrders).map((item) => [item.platformOrderId, item])) - - for (const item of Array.isArray(results) ? results : []) { - const platformOrderId = String(item?.platformOrderId || '').trim() - if (!platformOrderId || item?.ignored) { - continue - } - - const payStatus = normalizePayStatus(item?.payStatus) - if (payStatus === 'paid') { - nextMap.delete(platformOrderId) - continue - } - - if (payStatus !== 'unpaid') { - continue - } - - const previous = nextMap.get(platformOrderId) - nextMap.set(platformOrderId, { - platformOrderId, - shopId: String(item?.shopId || previous?.shopId || '').trim(), - shopName: String(item?.shopName || previous?.shopName || '').trim(), - itemTitle: String(item?.itemTitle || previous?.itemTitle || '').trim(), - payStatus, - detectedAt: previous?.detectedAt || now, - lastSeenAt: now, - }) - } - - return [...nextMap.values()] -} - -export function hasActiveKhhaoWatchOrders(watchOrders = []) { - return normalizeKhhaoWatchOrders(watchOrders).length > 0 -} - -function normalizeKhhaoWatchOrders(value) { - return (Array.isArray(value) ? value : []) - .map((item) => ({ - platformOrderId: String(item?.platformOrderId || '').trim(), - shopId: String(item?.shopId || '').trim(), - shopName: String(item?.shopName || '').trim(), - itemTitle: String(item?.itemTitle || '').trim(), - payStatus: normalizePayStatus(item?.payStatus), - detectedAt: String(item?.detectedAt || '').trim(), - lastSeenAt: String(item?.lastSeenAt || '').trim(), - })) - .filter((item) => item.platformOrderId && item.payStatus === 'unpaid') -} - -function normalizePayStatus(value) { - const normalized = String(value || '').trim().toLowerCase() - if (normalized === 'paid') { - return 'paid' - } - - return normalized === 'unpaid' ? 'unpaid' : 'unknown' -} - -function resolveLatestOrderCreatedAt(results = []) { - let latestText = '' - let latestTime = 0 - - for (const item of Array.isArray(results) ? results : []) { - const text = String(item?.rawOrderCreatedAt || item?.orderCreatedAt || '').trim() - const timestamp = parseKhhaoOrderCreatedAt(text) - if (Number.isFinite(timestamp) && timestamp > latestTime) { - latestTime = timestamp - latestText = new Date(timestamp).toISOString() - } - } - - return latestText -} - -function parseKhhaoOrderCreatedAt(value) { - const text = String(value || '').trim() - if (!text) { - return NaN - } - - return Date.parse(`${text.replace(' ', 'T')}+08:00`) -} diff --git a/apps/backend/src/services/platforms/khhao/auto-sync-service.test.js b/apps/backend/src/services/platforms/khhao/auto-sync-service.test.js deleted file mode 100644 index 72f341ee..00000000 --- a/apps/backend/src/services/platforms/khhao/auto-sync-service.test.js +++ /dev/null @@ -1,79 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' - -import { - hasActiveKhhaoWatchOrders, - reconcileKhhaoWatchOrders, -} from './auto-sync-service.js' - -test('reconcileKhhaoWatchOrders adds new unpaid synced order into watch list', () => { - const now = '2026-05-03T12:00:00.000Z' - const next = reconcileKhhaoWatchOrders([], [ - { - platformOrderId: '2612300323173226', - shopId: '4269276762', - shopName: '稚嫩游戏交易店', - itemTitle: '暗影哥特', - payStatus: 'unpaid', - ignored: false, - }, - ], now) - - assert.equal(next.length, 1) - assert.deepEqual(next[0], { - platformOrderId: '2612300323173226', - shopId: '4269276762', - shopName: '稚嫩游戏交易店', - itemTitle: '暗影哥特', - payStatus: 'unpaid', - detectedAt: now, - lastSeenAt: now, - }) - assert.equal(hasActiveKhhaoWatchOrders(next), true) -}) - -test('reconcileKhhaoWatchOrders removes watched order after payment is detected', () => { - const current = [ - { - platformOrderId: '2612300323173226', - shopId: '4269276762', - shopName: '稚嫩游戏交易店', - itemTitle: '暗影哥特', - payStatus: 'unpaid', - detectedAt: '2026-05-03T12:00:00.000Z', - lastSeenAt: '2026-05-03T12:00:00.000Z', - }, - ] - - const next = reconcileKhhaoWatchOrders(current, [ - { - platformOrderId: '2612300323173226', - shopId: '4269276762', - shopName: '稚嫩游戏交易店', - itemTitle: '暗影哥特', - payStatus: 'paid', - ignored: false, - }, - ], '2026-05-03T12:00:04.000Z') - - assert.deepEqual(next, []) - assert.equal(hasActiveKhhaoWatchOrders(next), false) -}) - -test('reconcileKhhaoWatchOrders ignores ignored and unknown-pay-status rows', () => { - const now = '2026-05-03T12:00:00.000Z' - const next = reconcileKhhaoWatchOrders([], [ - { - platformOrderId: 'ignored-order', - payStatus: 'unpaid', - ignored: true, - }, - { - platformOrderId: 'unknown-order', - payStatus: 'unknown', - ignored: false, - }, - ], now) - - assert.deepEqual(next, []) -}) diff --git a/apps/backend/src/services/platforms/khhao/captcha-service.js b/apps/backend/src/services/platforms/khhao/captcha-service.js deleted file mode 100644 index 0fed71f1..00000000 --- a/apps/backend/src/services/platforms/khhao/captcha-service.js +++ /dev/null @@ -1,75 +0,0 @@ -// @ts-check - -import { recognizeImageCaptcha } from '../../session/ocr.js' -import { buildKhhaoUrl, buildCookieHeader, normalizeKhhaoCookieState, resolveKhhaoConfig } from './shared.js' - -/** - * @param {{ - * baseUrl?: string - * captchaPath?: string - * timeoutMs?: number - * cookies?: Record - * requestId?: string - * includeImageBase64?: boolean - * }} [options] - */ -export async function fetchKhhaoCaptcha(options = {}) { - const config = resolveKhhaoConfig(options) - const cookieHeader = buildCookieHeader(options.cookies) - const response = await fetchWithTimeout(buildKhhaoUrl(config.baseUrl, config.captchaPath), { - method: 'GET', - headers: { - accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8', - referer: buildKhhaoUrl(config.baseUrl, config.loginPath).toString(), - ...(cookieHeader ? { cookie: cookieHeader } : {}), - }, - }, config.timeoutMs) - - if (!response.ok) { - throw new Error(`khhao 验证码请求失败,HTTP ${response.status}`) - } - - const imageBuffer = Buffer.from(await response.arrayBuffer()) - const imageBase64 = imageBuffer.toString('base64') - const ocr = await recognizeImageCaptcha({ - imageBase64, - imageContentType: String(response.headers.get('content-type') || 'image/png').trim() || 'image/png', - tag: options.requestId ? `khhao-${options.requestId}` : 'khhao-login-captcha', - }) - - if (ocr?.code !== 0) { - throw new Error(ocr?.msg || 'khhao 验证码 OCR 识别失败') - } - - const captchaText = String(ocr?.data?.text || ocr?.data?.recognizedText || '').trim() - if (!captchaText) { - throw new Error('khhao 验证码 OCR 未识别出内容') - } - - return { - captchaText, - ocr, - cookieMap: normalizeKhhaoCookieState(options.cookies, response.headers), - contentType: String(response.headers.get('content-type') || 'image/png').trim() || 'image/png', - imageBase64: options.includeImageBase64 ? imageBase64 : '', - } -} - -async function fetchWithTimeout(input, init, timeoutMs) { - const controller = new AbortController() - const timer = setTimeout(() => controller.abort(), timeoutMs) - - try { - return await fetch(input, { - ...init, - signal: controller.signal, - }) - } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - throw new Error(`khhao 验证码请求超时(${timeoutMs}ms)`) - } - throw error - } finally { - clearTimeout(timer) - } -} diff --git a/apps/backend/src/services/platforms/khhao/order-mapper-service.js b/apps/backend/src/services/platforms/khhao/order-mapper-service.js deleted file mode 100644 index 633380ea..00000000 --- a/apps/backend/src/services/platforms/khhao/order-mapper-service.js +++ /dev/null @@ -1,169 +0,0 @@ -// @ts-check - -import { parseAmountToFen } from '../../../utils/money.js' -import { resolveKuaishouEticketShopConfig } from '../kuaishou-eticket/source-config-service.js' - -export function mapKhhaoOrderPreviewList(items = []) { - return (Array.isArray(items) ? items : []).map((item) => mapKhhaoOrderPreview(item)) -} - -export function mapKhhaoOrderToSourceEvent(item = {}) { - const preview = mapKhhaoOrderPreview(item) - const orderStatus = resolveKhhaoOrderStatus(preview.status) - const payStatus = resolveKhhaoPayStatus(preview.status) - const sourceShopId = String(preview.khhaoShopId || preview.shopId || '').trim() - - return { - provider: 'khhao', - platform: preview.platform || 'unknown', - shopId: sourceShopId, - shopIdAliases: uniqueNonEmptyValues([sourceShopId, preview.shopId, ...(preview.shopIdAliases || [])]), - shopName: preview.shopName, - platformOrderId: preview.platformOrderId, - orderStatus, - payStatus, - buyerId: '', - buyerName: String(preview.raw.name || '').trim(), - receiverContact: '', - totalAmount: preview.totalAmountFen, - currency: 'CNY', - paidAt: payStatus === 'paid' ? normalizePaidAt(preview.orderCreatedAt) : null, - rawPayload: preview.raw, - items: [ - { - itemId: preview.itemId, - externalItemId: preview.itemId, - externalSkuCode: preview.skuCode || preview.itemId, - externalSkuName: preview.itemTitle || preview.skuCode || preview.itemId, - skuCode: preview.skuCode || preview.itemId, - skuName: preview.itemTitle || preview.skuCode || preview.itemId, - quantity: preview.quantity, - snapshot: preview.raw, - spec: preview.raw, - }, - ], - } -} - -export function mapKhhaoOrderPreview(item = {}) { - const raw = isPlainObject(item) ? item : {} - const platform = resolveKhhaoPlatform(raw.pingtai) - const quantity = normalizeQuantity(raw.num) - const internalShopId = String(raw.shopid || '').trim() - const shopName = String(raw.shopName || '').trim() - const resolvedShopIdentity = resolveKhhaoShopIdentity({ - platform, - internalShopId, - shopName, - }) - - return { - provider: 'khhao', - platform, - platformLabel: String(raw.pingtaiName || '').trim(), - platformOrderId: String(raw.ordersn || '').trim(), - shopId: resolvedShopIdentity.shopId, - kuaishouShopId: resolvedShopIdentity.officialShopId, - shopIdAliases: resolvedShopIdentity.shopIdAliases, - khhaoShopId: internalShopId, - internalShopId, - shopName, - itemId: String(raw.goodid || '').trim(), - itemTitle: String(raw.goodName || '').trim(), - skuCode: String(raw.sku || '').trim(), - quantity, - totalAmountFen: parseAmountToFen(raw.fee), - status: String(raw.status || '').trim(), - statusLabel: stripHtmlTags(raw.statusName), - orderCreatedAt: String(raw.addtime || '').trim(), - raw, - } -} - -function resolveKhhaoShopIdentity({ platform = '', internalShopId = '', shopName = '' } = {}) { - const fallbackShopId = String(internalShopId || '').trim() - const normalizedShopName = String(shopName || '').trim() - - if (String(platform || '').trim() !== 'kuaishou') { - return { - shopId: fallbackShopId, - officialShopId: '', - shopIdAliases: fallbackShopId ? [fallbackShopId] : [], - } - } - - const matchedShop = resolveKuaishouEticketShopConfig({ - shopId: fallbackShopId, - shopName: normalizedShopName, - }) - const resolvedShopId = String(matchedShop?.shopId || '').trim() || fallbackShopId - const shopIdAliases = uniqueNonEmptyValues([resolvedShopId, fallbackShopId]) - - return { - shopId: resolvedShopId, - officialShopId: String(matchedShop?.shopId || '').trim(), - shopIdAliases, - } -} - -export function resolveKhhaoPlatform(value) { - const normalized = String(value || '').trim() - - if (normalized === '3') { - return 'kuaishou' - } - - return normalized ? 'unknown' : '' -} - -export function resolveKhhaoOrderStatus(value) { - const normalized = String(value || '').trim() - - if (normalized === '2') { - return 'paid' - } - - if (normalized === '0') { - return 'created' - } - - return 'unknown' -} - -export function resolveKhhaoPayStatus(value) { - const normalized = String(value || '').trim() - - if (normalized === '2') { - return 'paid' - } - - if (normalized === '0') { - return 'unpaid' - } - - return 'unknown' -} - -function normalizeQuantity(value) { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed > 0 ? parsed : 1 -} - -function stripHtmlTags(value) { - return String(value || '').replace(/<[^>]+>/g, '').trim() -} - -function normalizePaidAt(value) { - const text = String(value || '').trim() - return text ? text.replace(' ', 'T') : null -} - -function isPlainObject(value) { - return Object.prototype.toString.call(value) === '[object Object]' -} - -function uniqueNonEmptyValues(values) { - return [...new Set((Array.isArray(values) ? values : []) - .map((value) => String(value || '').trim()) - .filter(Boolean))] -} diff --git a/apps/backend/src/services/platforms/khhao/order-query-service.js b/apps/backend/src/services/platforms/khhao/order-query-service.js deleted file mode 100644 index 0345cd71..00000000 --- a/apps/backend/src/services/platforms/khhao/order-query-service.js +++ /dev/null @@ -1,112 +0,0 @@ -// @ts-check - -import { createHttpError } from '../../../utils/http.js' -import { buildKhhaoUrl, resolveKhhaoConfig } from './shared.js' - -/** - * @param {{ - * session?: { - * baseUrl?: string - * cookieHeader?: string - * } - * page?: number | string - * limit?: number | string - * baseUrl?: string - * orderListPath?: string - * timeoutMs?: number - * }} [payload] - */ -export async function queryKhhaoOrderList(payload = {}) { - const config = resolveKhhaoConfig(payload) - const page = normalizePage(payload.page) - const limit = normalizeLimit(payload.limit) - const session = payload.session || {} - const cookieHeader = String(session.cookieHeader || '').trim() - - if (!cookieHeader) { - throw createHttpError('khhao 查询订单缺少登录态 Cookie', { - statusCode: 400, - errorCode: 'khhao_query_missing_cookie', - }) - } - - const response = await fetchWithTimeout(buildKhhaoUrl(config.baseUrl, config.orderListPath, { page, limit }), { - method: 'GET', - headers: { - accept: 'application/json, text/javascript, */*; q=0.01', - referer: buildKhhaoUrl(config.baseUrl, config.loginPath).toString(), - 'x-requested-with': 'XMLHttpRequest', - cookie: cookieHeader, - }, - }, config.timeoutMs) - - const rawText = await response.text() - if (!response.ok) { - throw createHttpError(`khhao 订单查询失败,HTTP ${response.status}`, { - statusCode: 502, - errorCode: 'khhao_query_http_failed', - }) - } - - const payloadJson = tryParseJson(rawText) - if (!payloadJson || typeof payloadJson !== 'object') { - throw createHttpError('khhao 订单查询返回了无法解析的 JSON', { - statusCode: 502, - errorCode: 'khhao_query_invalid_json', - }) - } - - const items = Array.isArray(payloadJson.data) ? payloadJson.data : [] - - return { - page, - limit, - total: Number(payloadJson.count || items.length || 0), - items, - raw: payloadJson, - } -} - -function normalizePage(value) { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed > 0 ? parsed : 1 -} - -function normalizeLimit(value) { - const parsed = Number(value) - if (!Number.isInteger(parsed) || parsed <= 0) { - return 50 - } - - return Math.min(parsed, 200) -} - -async function fetchWithTimeout(input, init, timeoutMs) { - const controller = new AbortController() - const timer = setTimeout(() => controller.abort(), timeoutMs) - - try { - return await fetch(input, { - ...init, - signal: controller.signal, - }) - } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - throw createHttpError(`khhao 订单查询超时(${timeoutMs}ms)`, { - statusCode: 504, - errorCode: 'khhao_query_timeout', - }) - } - throw error - } finally { - clearTimeout(timer) - } -} - -function tryParseJson(text) { - try { - return JSON.parse(text) - } catch { - return null - } -} diff --git a/apps/backend/src/services/platforms/khhao/order-sync-service.js b/apps/backend/src/services/platforms/khhao/order-sync-service.js deleted file mode 100644 index b5e1fd15..00000000 --- a/apps/backend/src/services/platforms/khhao/order-sync-service.js +++ /dev/null @@ -1,150 +0,0 @@ -// @ts-check - -import { upsertOrderFromSource } from '../../order/order-service.js' -import { - mapKhhaoOrderPreview, - mapKhhaoOrderToSourceEvent, - resolveKhhaoOrderStatus, - resolveKhhaoPayStatus, -} from './order-mapper-service.js' -import { queryKhhaoOrderList } from './order-query-service.js' -import { loginKhhaoSession } from './session-service.js' - -/** - * @param {{ - * baseUrl?: string - * username?: string - * password?: string - * page?: number | string - * limit?: number | string - * pages?: number | string - * maxCaptchaAttempts?: number | string - * createdAfter?: string - * }} [payload] - */ -export async function syncKhhaoOrders(payload = {}) { - const session = await loginKhhaoSession({ - baseUrl: payload.baseUrl, - username: payload.username, - password: payload.password, - maxCaptchaAttempts: payload.maxCaptchaAttempts, - requestId: `admin-khhao-sync-orders:${String(payload.username || '').trim() || 'anonymous'}`, - }) - - const queryResult = await queryKhhaoOrderList({ - baseUrl: payload.baseUrl, - page: 1, - limit: resolvePageSize(payload.limit), - session, - }) - - const createdAfter = String(payload.createdAfter || '').trim() - const pages = Math.max(1, Math.min(Number(payload.pages || payload.page || 1) || 1, 5)) - const results = [] - const allItems = [...queryResult.items] - - for (let page = 2; page <= pages; page += 1) { - const nextPage = await queryKhhaoOrderList({ - baseUrl: payload.baseUrl, - page, - limit: resolvePageSize(payload.limit), - session, - }) - allItems.push(...nextPage.items) - } - - for (const item of allItems) { - const preview = mapKhhaoOrderPreview(item) - - if (createdAfter && !isKhhaoOrderAfter(preview.orderCreatedAt, createdAfter)) { - results.push({ - platformOrderId: preview.platformOrderId, - provider: 'khhao', - platform: preview.platform || 'unknown', - shopId: preview.shopId, - shopName: preview.shopName, - skuCode: preview.skuCode, - itemTitle: preview.itemTitle, - orderStatus: resolvePreviewOrderStatus(preview), - payStatus: resolvePreviewPayStatus(preview), - rawOrderCreatedAt: preview.orderCreatedAt, - ignored: true, - ignoreReason: 'before_sync_baseline', - orderId: null, - orderItemCount: 0, - taskCount: 0, - }) - continue - } - - const sourceEvent = mapKhhaoOrderToSourceEvent(item) - const upsertResult = await upsertOrderFromSource(sourceEvent, { sourceLabel: 'khhao-sync' }) - - results.push({ - platformOrderId: preview.platformOrderId, - provider: sourceEvent.provider, - platform: sourceEvent.platform, - shopId: sourceEvent.shopId, - shopName: sourceEvent.shopName, - skuCode: preview.skuCode, - itemTitle: preview.itemTitle, - orderStatus: sourceEvent.orderStatus, - payStatus: sourceEvent.payStatus, - rawOrderCreatedAt: preview.orderCreatedAt, - ignored: Boolean(upsertResult.ignored), - ignoreReason: String(upsertResult.ignoreReason || '').trim(), - orderId: Number(upsertResult.order?.id || 0) || null, - orderItemCount: Array.isArray(upsertResult.orderItems) ? upsertResult.orderItems.length : 0, - taskCount: Array.isArray(upsertResult.tasks) ? upsertResult.tasks.length : 0, - }) - } - - return { - baseUrl: session.baseUrl, - page: 1, - limit: resolvePageSize(payload.limit), - total: queryResult.total, - fetchedCount: allItems.length, - syncedCount: results.filter((item) => !item.ignored && item.orderId).length, - ignoredCount: results.filter((item) => item.ignored).length, - results, - } -} - -function resolvePageSize(value) { - const parsed = Number(value) - if (!Number.isInteger(parsed) || parsed <= 0) { - return 20 - } - - return Math.min(parsed, 50) -} - -function isKhhaoOrderAfter(orderCreatedAt, baselineIso) { - const orderTime = parseKhhaoOrderCreatedAt(orderCreatedAt) - const baselineTime = Date.parse(String(baselineIso || '').trim()) - - if (!Number.isFinite(orderTime) || !Number.isFinite(baselineTime)) { - return true - } - - return orderTime > baselineTime -} - -function parseKhhaoOrderCreatedAt(value) { - const text = String(value || '').trim() - if (!text) { - return NaN - } - - const isoText = text.replace(' ', 'T') - return Date.parse(`${isoText}+08:00`) -} - -function resolvePreviewOrderStatus(preview) { - return resolveKhhaoOrderStatus(preview?.status) -} - -function resolvePreviewPayStatus(preview) { - return resolveKhhaoPayStatus(preview?.status) -} diff --git a/apps/backend/src/services/platforms/khhao/session-service.js b/apps/backend/src/services/platforms/khhao/session-service.js deleted file mode 100644 index 14f7a4f2..00000000 --- a/apps/backend/src/services/platforms/khhao/session-service.js +++ /dev/null @@ -1,216 +0,0 @@ -// @ts-check - -import { createHttpError } from '../../../utils/http.js' -import { logInfo, logWarn } from '../../../utils/logger.js' -import { fetchKhhaoCaptcha } from './captcha-service.js' -import { - buildCookieHeader, - buildKhhaoUrl, - normalizeKhhaoCookieState, - resolveKhhaoConfig, -} from './shared.js' - -/** - * @param {{ - * username?: string - * password?: string - * baseUrl?: string - * timeoutMs?: number - * maxCaptchaAttempts?: number | string - * includeImageBase64?: boolean - * requestId?: string - * }} [payload] - */ -export async function loginKhhaoSession(payload = {}) { - const username = String(payload.username || '').trim() - const password = String(payload.password || '').trim() - const requestId = String(payload.requestId || '').trim() - - if (!username) { - throw createHttpError('khhao 登录缺少账号', { - statusCode: 400, - errorCode: 'khhao_login_missing_username', - }) - } - - if (!password) { - throw createHttpError('khhao 登录缺少密码', { - statusCode: 400, - errorCode: 'khhao_login_missing_password', - }) - } - - const config = resolveKhhaoConfig(payload) - const maxCaptchaAttempts = normalizeAttempts(payload.maxCaptchaAttempts) - /** @type {Record} */ - let cookieMap = {} - let lastFailureMessage = '' - - cookieMap = await openKhhaoLoginPage(config, cookieMap) - - for (let attempt = 1; attempt <= maxCaptchaAttempts; attempt += 1) { - const captcha = await fetchKhhaoCaptcha({ - ...config, - cookies: cookieMap, - requestId: requestId ? `${requestId}-captcha-${attempt}` : `login-${attempt}`, - includeImageBase64: Boolean(payload.includeImageBase64), - }) - cookieMap = captcha.cookieMap - - const result = await submitKhhaoLogin({ - config, - cookieMap, - username, - password, - captchaText: normalizeKhhaoCaptchaText(captcha.captchaText), - requestId, - }) - - cookieMap = result.cookieMap - - if (result.success) { - logInfo('[khhao/session]', 'khhao 登录成功', { - requestId, - username, - attempt, - cookieKeys: Object.keys(cookieMap), - }) - - return { - baseUrl: config.baseUrl, - cookieMap, - cookieHeader: buildCookieHeader(cookieMap), - loggedInAt: new Date().toISOString(), - username, - attempt, - captchaText: captcha.captchaText, - captchaImageBase64: captcha.imageBase64, - responseMessage: result.message, - } - } - - lastFailureMessage = result.message - logWarn('[khhao/session]', 'khhao 登录失败', { - requestId, - username, - attempt, - message: result.message, - }) - - if (!shouldRetryLogin(result.message) || attempt >= maxCaptchaAttempts) { - break - } - } - - throw createHttpError(lastFailureMessage || 'khhao 登录失败', { - statusCode: 401, - errorCode: 'khhao_login_failed', - }) -} - -async function openKhhaoLoginPage(config, currentCookies) { - const response = await fetchWithTimeout(buildKhhaoUrl(config.baseUrl, config.loginPath), { - method: 'GET', - headers: { - accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - }, - }, config.timeoutMs) - - if (!response.ok) { - throw createHttpError(`khhao 登录页打开失败,HTTP ${response.status}`, { - statusCode: 502, - errorCode: 'khhao_login_page_failed', - }) - } - - return normalizeKhhaoCookieState(currentCookies, response.headers) -} - -async function submitKhhaoLogin({ config, cookieMap, username, password, captchaText, requestId }) { - const response = await fetchWithTimeout(buildKhhaoUrl(config.baseUrl, config.loginPath), { - method: 'POST', - headers: { - accept: 'application/json, text/javascript, */*; q=0.01', - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - origin: config.baseUrl, - referer: buildKhhaoUrl(config.baseUrl, config.loginPath).toString(), - 'x-requested-with': 'XMLHttpRequest', - ...(buildCookieHeader(cookieMap) ? { cookie: buildCookieHeader(cookieMap) } : {}), - }, - body: new URLSearchParams({ - username, - password, - img_code: captchaText, - }).toString(), - }, config.timeoutMs) - - const rawText = await response.text() - const cookieState = normalizeKhhaoCookieState(cookieMap, response.headers) - - if (!response.ok) { - throw createHttpError(`khhao 登录请求失败,HTTP ${response.status}`, { - statusCode: 502, - errorCode: 'khhao_login_request_failed', - }) - } - - const payload = tryParseJson(rawText) - const errcode = Number(payload?.errcode ?? 1) - const message = String(payload?.msg || '').trim() || 'khhao 登录失败' - - return { - success: errcode === 0, - message, - payload, - cookieMap: cookieState, - requestId, - } -} - -function shouldRetryLogin(message) { - const normalized = String(message || '').trim() - return normalized.includes('验证码') -} - -function normalizeAttempts(value) { - const parsed = Number(value) - if (!Number.isInteger(parsed) || parsed <= 0) { - return 3 - } - - return Math.min(parsed, 5) -} - -function normalizeKhhaoCaptchaText(value) { - return String(value || '').trim().toLowerCase() -} - -async function fetchWithTimeout(input, init, timeoutMs) { - const controller = new AbortController() - const timer = setTimeout(() => controller.abort(), timeoutMs) - - try { - return await fetch(input, { - ...init, - signal: controller.signal, - }) - } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - throw createHttpError(`khhao 请求超时(${timeoutMs}ms)`, { - statusCode: 504, - errorCode: 'khhao_request_timeout', - }) - } - throw error - } finally { - clearTimeout(timer) - } -} - -function tryParseJson(text) { - try { - return JSON.parse(text) - } catch { - return null - } -} diff --git a/apps/backend/src/services/platforms/khhao/shared.js b/apps/backend/src/services/platforms/khhao/shared.js deleted file mode 100644 index 66797b94..00000000 --- a/apps/backend/src/services/platforms/khhao/shared.js +++ /dev/null @@ -1,113 +0,0 @@ -// @ts-check - -import { runtimeConfig } from '../../../config/runtime.js' - -/** @typedef {import('../../../types/runtime-config.js').RuntimeConfig} RuntimeConfig */ - -export function resolveKhhaoConfig(overrides = {}) { - /** @type {RuntimeConfig['platforms']['khhao']} */ - const baseConfig = runtimeConfig.platforms?.khhao || { - baseUrl: '', - timeoutMs: 5000, - loginPath: '/c/login/index.php', - captchaPath: '/verify_img.php', - orderListPath: '/c/payOrder/get.php', - } - - return { - baseUrl: normalizeBaseUrl(overrides.baseUrl || baseConfig.baseUrl), - timeoutMs: normalizePositiveInteger(overrides.timeoutMs || baseConfig.timeoutMs, 5000), - loginPath: normalizePath(overrides.loginPath || baseConfig.loginPath, '/c/login/index.php'), - captchaPath: normalizePath(overrides.captchaPath || baseConfig.captchaPath, '/verify_img.php'), - orderListPath: normalizePath(overrides.orderListPath || baseConfig.orderListPath, '/c/payOrder/get.php'), - } -} - -export function buildKhhaoUrl(baseUrl, pathname, searchParams = null) { - const url = new URL(normalizePath(pathname, '/'), normalizeBaseUrl(baseUrl) || 'https://admin.khhao.com') - - if (searchParams && typeof searchParams === 'object') { - for (const [key, value] of Object.entries(searchParams)) { - if (typeof value === 'undefined' || value === null || value === '') { - continue - } - url.searchParams.set(key, String(value)) - } - } - - return url -} - -export function collectSetCookieHeaders(headers) { - if (!headers || typeof headers !== 'object') { - return [] - } - - if (typeof headers.getSetCookie === 'function') { - return headers.getSetCookie() - } - - const raw = headers.get('set-cookie') - return raw ? [raw] : [] -} - -export function mergeCookies(currentCookies = {}, setCookieHeaders = []) { - const next = { ...currentCookies } - - for (const header of Array.isArray(setCookieHeaders) ? setCookieHeaders : []) { - const text = String(header || '').trim() - if (!text) { - continue - } - - const pair = text.split(';', 1)[0] - const separatorIndex = pair.indexOf('=') - if (separatorIndex <= 0) { - continue - } - - const key = pair.slice(0, separatorIndex).trim() - const value = pair.slice(separatorIndex + 1).trim() - - if (!key) { - continue - } - - if (value) { - next[key] = value - } else { - delete next[key] - } - } - - return next -} - -export function buildCookieHeader(cookieMap = {}) { - return Object.entries(cookieMap) - .filter(([key, value]) => String(key || '').trim() && String(value || '').trim()) - .map(([key, value]) => `${key}=${value}`) - .join('; ') -} - -export function normalizeKhhaoCookieState(currentCookies = {}, headers) { - return mergeCookies(currentCookies, collectSetCookieHeaders(headers)) -} - -function normalizeBaseUrl(value) { - return String(value || '').trim().replace(/\/+$/, '') -} - -function normalizePath(value, fallback) { - const normalized = String(value || '').trim() - if (!normalized) { - return fallback - } - - return normalized.startsWith('/') ? normalized : `/${normalized}` -} - -function normalizePositiveInteger(value, fallback) { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback -} diff --git a/apps/backend/src/services/platforms/khhao/source-config-service.js b/apps/backend/src/services/platforms/khhao/source-config-service.js deleted file mode 100644 index 450f6d3e..00000000 --- a/apps/backend/src/services/platforms/khhao/source-config-service.js +++ /dev/null @@ -1,80 +0,0 @@ -// @ts-check - -import fs from 'node:fs' -import path from 'node:path' - -import { PROJECT_ROOT } from '../../../config/runtime.js' - -const KHHAO_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'khhao-sources.json') - -export function getKhhaoSourcesFilePath() { - return KHHAO_SOURCES_FILE_PATH -} - -export function getKhhaoSourceConfig() { - return loadKhhaoSourceConfigFromFile() -} - -export function saveKhhaoSourceConfig(rawValue) { - const normalized = normalizeKhhaoSourceConfig(rawValue) - fs.mkdirSync(path.dirname(KHHAO_SOURCES_FILE_PATH), { recursive: true }) - fs.writeFileSync(KHHAO_SOURCES_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') - return normalized -} - -function loadKhhaoSourceConfigFromFile() { - if (!fs.existsSync(KHHAO_SOURCES_FILE_PATH)) { - return createDefaultKhhaoSourceConfig() - } - - try { - const rawText = fs.readFileSync(KHHAO_SOURCES_FILE_PATH, 'utf8') - return normalizeKhhaoSourceConfig(JSON.parse(rawText)) - } catch { - return createDefaultKhhaoSourceConfig() - } -} - -function normalizeKhhaoSourceConfig(rawValue) { - const source = isPlainObject(rawValue) ? rawValue : {} - - return { - enabled: typeof source.enabled === 'boolean' ? source.enabled : true, - baseUrl: String(source.baseUrl || 'https://admin.khhao.com').trim() || 'https://admin.khhao.com', - username: String(source.username || '').trim(), - password: String(source.password || '').trim(), - maxCaptchaAttempts: normalizePositiveInteger(source.maxCaptchaAttempts, 3), - autoSync: normalizeAutoSyncConfig(source.autoSync), - } -} - -function createDefaultKhhaoSourceConfig() { - return { - enabled: true, - baseUrl: 'https://admin.khhao.com', - username: '', - password: '', - maxCaptchaAttempts: 3, - autoSync: normalizeAutoSyncConfig({}), - } -} - -function normalizeAutoSyncConfig(rawValue) { - const source = isPlainObject(rawValue) ? rawValue : {} - - return { - enabled: typeof source.enabled === 'boolean' ? source.enabled : false, - intervalMinutes: normalizePositiveInteger(source.intervalMinutes, 3), - pages: normalizePositiveInteger(source.pages, 2), - pageSize: normalizePositiveInteger(source.pageSize, 20), - } -} - -function normalizePositiveInteger(value, fallback) { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback -} - -function isPlainObject(value) { - return Object.prototype.toString.call(value) === '[object Object]' -} diff --git a/apps/backend/src/services/platforms/khhao/sync-state-service.js b/apps/backend/src/services/platforms/khhao/sync-state-service.js deleted file mode 100644 index 74a3d0dc..00000000 --- a/apps/backend/src/services/platforms/khhao/sync-state-service.js +++ /dev/null @@ -1,129 +0,0 @@ -// @ts-check - -import fs from 'node:fs' -import path from 'node:path' - -import { PROJECT_ROOT } from '../../../config/runtime.js' -import { nowIso } from '../../../utils/time.js' - -const KHHAO_SYNC_STATE_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'khhao-sync-state.json') - -export function getKhhaoSyncStateFilePath() { - return KHHAO_SYNC_STATE_FILE_PATH -} - -export function getKhhaoSyncState() { - return loadKhhaoSyncStateFromFile() -} - -export function saveKhhaoSyncState(rawValue) { - const normalized = normalizeKhhaoSyncState(rawValue) - fs.mkdirSync(path.dirname(KHHAO_SYNC_STATE_FILE_PATH), { recursive: true }) - fs.writeFileSync(KHHAO_SYNC_STATE_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8') - return normalized -} - -export function ensureKhhaoSyncBaseline() { - const current = getKhhaoSyncState() - if (String(current.syncFromCreatedAt || '').trim()) { - return current - } - - return saveKhhaoSyncState({ - ...current, - syncFromCreatedAt: nowIso(), - }) -} - -function loadKhhaoSyncStateFromFile() { - if (!fs.existsSync(KHHAO_SYNC_STATE_FILE_PATH)) { - return createDefaultKhhaoSyncState() - } - - try { - return normalizeKhhaoSyncState(JSON.parse(fs.readFileSync(KHHAO_SYNC_STATE_FILE_PATH, 'utf8'))) - } catch { - return createDefaultKhhaoSyncState() - } -} - -function createDefaultKhhaoSyncState() { - return { - syncFromCreatedAt: '', - lastRunStartedAt: '', - lastRunFinishedAt: '', - lastRunStatus: 'idle', - lastErrorMessage: '', - fetchedCount: 0, - syncedCount: 0, - ignoredCount: 0, - lastOrderCreatedAt: '', - watchMode: 'normal', - watchOrders: [], - } -} - -function normalizeKhhaoSyncState(rawValue) { - const source = isPlainObject(rawValue) ? rawValue : {} - - return { - syncFromCreatedAt: String(source.syncFromCreatedAt || '').trim(), - lastRunStartedAt: String(source.lastRunStartedAt || '').trim(), - lastRunFinishedAt: String(source.lastRunFinishedAt || '').trim(), - lastRunStatus: normalizeStatus(source.lastRunStatus), - lastErrorMessage: String(source.lastErrorMessage || '').trim(), - fetchedCount: normalizeNonNegativeInteger(source.fetchedCount), - syncedCount: normalizeNonNegativeInteger(source.syncedCount), - ignoredCount: normalizeNonNegativeInteger(source.ignoredCount), - lastOrderCreatedAt: String(source.lastOrderCreatedAt || '').trim(), - watchMode: normalizeWatchMode(source.watchMode), - watchOrders: normalizeWatchOrders(source.watchOrders), - } -} - -function normalizeStatus(value) { - const normalized = String(value || '').trim() - return normalized || 'idle' -} - -function normalizeNonNegativeInteger(value) { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0 -} - -function normalizeWatchMode(value) { - return String(value || '').trim() === 'boosted' ? 'boosted' : 'normal' -} - -function normalizeWatchOrders(value) { - return (Array.isArray(value) ? value : []) - .map((item) => normalizeWatchOrder(item)) - .filter((item) => item.platformOrderId) -} - -function normalizeWatchOrder(value) { - const source = isPlainObject(value) ? value : {} - - return { - platformOrderId: String(source.platformOrderId || '').trim(), - shopId: String(source.shopId || '').trim(), - shopName: String(source.shopName || '').trim(), - itemTitle: String(source.itemTitle || '').trim(), - payStatus: normalizeWatchPayStatus(source.payStatus), - detectedAt: String(source.detectedAt || '').trim(), - lastSeenAt: String(source.lastSeenAt || '').trim(), - } -} - -function normalizeWatchPayStatus(value) { - const normalized = String(value || '').trim().toLowerCase() - if (normalized === 'paid') { - return 'paid' - } - - return normalized === 'unpaid' ? 'unpaid' : 'unknown' -} - -function isPlainObject(value) { - return Object.prototype.toString.call(value) === '[object Object]' -} diff --git a/apps/backend/src/types/admin-route-inputs.js b/apps/backend/src/types/admin-route-inputs.js index 31c52bc7..0e19662f 100644 --- a/apps/backend/src/types/admin-route-inputs.js +++ b/apps/backend/src/types/admin-route-inputs.js @@ -30,10 +30,6 @@ export {} * @typedef {import('./admin-read-inputs.js').AdminWebhookEventListQueryInput} AdminWebhookEventRouteQuery */ -/** - * @typedef {import('./admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigRouteBody - */ - /** * @typedef {import('./admin-write-inputs.js').AdminKuaishouEticketSourceConfigInput} AdminKuaishouEticketSourceConfigRouteBody */ @@ -54,14 +50,6 @@ export {} * @typedef {import('./admin-write-inputs.js').AdminKuaishouEticketShopInfoInput} AdminKuaishouEticketShopInfoRouteBody */ -/** - * @typedef {import('./admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginRouteBody - */ - -/** - * @typedef {import('./admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryRouteBody - */ - /** * @typedef {import('./admin-write-inputs.js').AdminCloudtentaclesSourceConfigInput} AdminCloudtentaclesSourceConfigRouteBody */ diff --git a/apps/backend/src/types/admin-write-inputs.js b/apps/backend/src/types/admin-write-inputs.js index cd46ac56..aefdb359 100644 --- a/apps/backend/src/types/admin-write-inputs.js +++ b/apps/backend/src/types/admin-write-inputs.js @@ -75,7 +75,6 @@ export {} * provider?: string * platform?: string * shopId?: string - * khhaoShopId?: string * internalSkuCode?: string * internalSkuName?: string * externalSkuCode?: string @@ -103,22 +102,6 @@ export {} * }} AdminKuaishouCloudFulfillmentConfigInput */ -/** - * @typedef {{ - * enabled?: boolean - * baseUrl?: string - * username?: string - * password?: string - * maxCaptchaAttempts?: number | string - * autoSync?: { - * enabled?: boolean - * intervalMinutes?: number | string - * pages?: number | string - * pageSize?: number | string - * } - * }} AdminKhhaoSourceConfigInput - */ - /** * @typedef {{ * shopId?: string @@ -167,27 +150,6 @@ export {} * }} AdminKuaishouEticketConsumeInput */ -/** - * @typedef {{ - * baseUrl?: string - * username?: string - * password?: string - * maxCaptchaAttempts?: number | string - * includeImageBase64?: boolean - * }} AdminKhhaoTestLoginInput - */ - -/** - * @typedef {{ - * baseUrl?: string - * username?: string - * password?: string - * page?: number | string - * limit?: number | string - * maxCaptchaAttempts?: number | string - * }} AdminKhhaoOrderQueryInput - */ - /** * @typedef {{ * enabled?: boolean @@ -303,7 +265,6 @@ export {} * platform?: string * shopId?: string * shopName?: string - * khhaoShopId?: string * skuCode?: string * skuName?: string * profileKey?: string diff --git a/apps/backend/src/types/runtime-config.js b/apps/backend/src/types/runtime-config.js index 1611ef0e..03d1f7e0 100644 --- a/apps/backend/src/types/runtime-config.js +++ b/apps/backend/src/types/runtime-config.js @@ -98,13 +98,6 @@ export {} * timestampToleranceSeconds: number * cardsEncoding: string * } - * khhao: { - * baseUrl: string - * timeoutMs: number - * loginPath: string - * captchaPath: string - * orderListPath: string - * } * cloudtentacles: { * baseUrl: string * timeoutMs: number diff --git a/apps/frontend/src/components.d.ts b/apps/frontend/src/components.d.ts index 64def359..734c7b65 100644 --- a/apps/frontend/src/components.d.ts +++ b/apps/frontend/src/components.d.ts @@ -14,7 +14,6 @@ declare module 'vue' { AdminPaginationBar: typeof import('./components/admin/AdminPaginationBar.vue')['default'] AdminPlatformAgisoSection: typeof import('./components/admin/AdminPlatformAgisoSection.vue')['default'] AdminPlatformCloudtentaclesSection: typeof import('./components/admin/AdminPlatformCloudtentaclesSection.vue')['default'] - AdminPlatformKhhaoSection: typeof import('./components/admin/AdminPlatformKhhaoSection.vue')['default'] AdminPlatformKuaishouEticketSection: typeof import('./components/admin/AdminPlatformKuaishouEticketSection.vue')['default'] AdminPlatformNinetyoneSection: typeof import('./components/admin/AdminPlatformNinetyoneSection.vue')['default'] AdminResultCard: typeof import('./components/admin/AdminResultCard.vue')['default'] diff --git a/apps/frontend/src/components/admin/AdminPlatformAgisoSection.vue b/apps/frontend/src/components/admin/AdminPlatformAgisoSection.vue index 97a8cd58..fb05ab88 100644 --- a/apps/frontend/src/components/admin/AdminPlatformAgisoSection.vue +++ b/apps/frontend/src/components/admin/AdminPlatformAgisoSection.vue @@ -31,7 +31,7 @@ defineProps()
职责 - 管理 Agiso 默认消息模板、店铺 AccessToken 以及店铺级模板覆盖。该区域不处理 khhao 登录与拉单。 + 管理 Agiso 默认消息模板、店铺 AccessToken 以及店铺级模板覆盖。
diff --git a/apps/frontend/src/components/admin/AdminPlatformKhhaoSection.vue b/apps/frontend/src/components/admin/AdminPlatformKhhaoSection.vue deleted file mode 100644 index a4669d5e..00000000 --- a/apps/frontend/src/components/admin/AdminPlatformKhhaoSection.vue +++ /dev/null @@ -1,264 +0,0 @@ - - - diff --git a/apps/frontend/src/composables/admin/platform-shops/types.ts b/apps/frontend/src/composables/admin/platform-shops/types.ts index c3169048..0452fd45 100644 --- a/apps/frontend/src/composables/admin/platform-shops/types.ts +++ b/apps/frontend/src/composables/admin/platform-shops/types.ts @@ -1,4 +1,4 @@ -export type PlatformTab = 'agiso' | 'khhao' | 'ninetyone' | 'kuaishouEticket' | 'cloudtentacles' +export type PlatformTab = 'agiso' | 'ninetyone' | 'kuaishouEticket' | 'cloudtentacles' export type EditableDefaults = { messageTemplate: string diff --git a/apps/frontend/src/composables/admin/platform-shops/useAdminKhhaoPlatform.ts b/apps/frontend/src/composables/admin/platform-shops/useAdminKhhaoPlatform.ts deleted file mode 100644 index 1fa32a5e..00000000 --- a/apps/frontend/src/composables/admin/platform-shops/useAdminKhhaoPlatform.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { computed, ref } from 'vue' - -import { showError, showSuccess } from '@/lib/feedback' -import { - queryAdminKhhaoOrders, - saveAdminKhhaoSourceConfig, - syncAdminKhhaoOrders, - testAdminKhhaoLogin, -} from '@/services/admin' -import type { - AdminKhhaoLoginTestResult, - AdminKhhaoOrderQueryResult, - AdminKhhaoOrderSyncResult, - AdminKhhaoSyncState, -} from '@/types/admin' - -export function useAdminKhhaoPlatform() { - const khhaoFilePath = ref('') - const khhaoStateFilePath = ref('') - const khhaoForm = ref({ - baseUrl: 'https://admin.khhao.com', - username: '', - password: '', - page: 1, - limit: 10, - maxCaptchaAttempts: 3, - autoSyncEnabled: false, - autoSyncIntervalMinutes: 3, - autoSyncPages: 2, - autoSyncPageSize: 20, - }) - const khhaoSaving = ref(false) - const khhaoTesting = ref(false) - const khhaoQuerying = ref(false) - const khhaoSyncing = ref(false) - const khhaoResultError = ref('') - const khhaoLoginResult = ref(null) - const khhaoQueryResult = ref(null) - const khhaoSyncResult = ref(null) - const khhaoAutoSyncState = ref(null) - - const khhaoStats = computed(() => ({ - hasCredential: Boolean(khhaoForm.value.username.trim() && khhaoForm.value.password.trim()), - queryCount: khhaoQueryResult.value?.itemCount || 0, - syncedCount: khhaoAutoSyncState.value?.syncedCount || khhaoSyncResult.value?.syncedCount || 0, - ignoredCount: khhaoAutoSyncState.value?.ignoredCount || khhaoSyncResult.value?.ignoredCount || 0, - })) - - function hydrateKhhaoConfig(data: { - filePath: string - stateFilePath: string - syncState: AdminKhhaoSyncState - source: { - baseUrl?: string - username?: string - password?: string - maxCaptchaAttempts?: number - autoSync?: { - enabled?: boolean - intervalMinutes?: number - pages?: number - pageSize?: number - } - } - }) { - khhaoFilePath.value = data.filePath - khhaoStateFilePath.value = data.stateFilePath - khhaoAutoSyncState.value = data.syncState - khhaoForm.value = { - baseUrl: data.source.baseUrl || 'https://admin.khhao.com', - username: data.source.username || '', - password: data.source.password || '', - page: 1, - limit: 10, - maxCaptchaAttempts: data.source.maxCaptchaAttempts || 3, - autoSyncEnabled: data.source.autoSync?.enabled === true, - autoSyncIntervalMinutes: data.source.autoSync?.intervalMinutes || 3, - autoSyncPages: data.source.autoSync?.pages || 2, - autoSyncPageSize: data.source.autoSync?.pageSize || 20, - } - } - - function buildKhhaoPayload() { - return { - baseUrl: khhaoForm.value.baseUrl.trim() || 'https://admin.khhao.com', - username: khhaoForm.value.username.trim(), - password: khhaoForm.value.password.trim(), - page: Number(khhaoForm.value.page || 1), - limit: Number(khhaoForm.value.limit || 10), - maxCaptchaAttempts: Number(khhaoForm.value.maxCaptchaAttempts || 3), - } - } - - function ensureKhhaoCredentials() { - if (!khhaoForm.value.username.trim() || !khhaoForm.value.password.trim()) { - khhaoResultError.value = '请先填写 khhao 账号和密码' - return false - } - return true - } - - async function handleKhhaoSaveSource() { - khhaoSaving.value = true - khhaoResultError.value = '' - - try { - const response = await saveAdminKhhaoSourceConfig({ - enabled: true, - baseUrl: khhaoForm.value.baseUrl.trim() || 'https://admin.khhao.com', - username: khhaoForm.value.username.trim(), - password: khhaoForm.value.password.trim(), - maxCaptchaAttempts: Number(khhaoForm.value.maxCaptchaAttempts || 3), - autoSync: { - enabled: Boolean(khhaoForm.value.autoSyncEnabled), - intervalMinutes: Number(khhaoForm.value.autoSyncIntervalMinutes || 3), - pages: Number(khhaoForm.value.autoSyncPages || 2), - pageSize: Number(khhaoForm.value.autoSyncPageSize || 20), - }, - }) - khhaoFilePath.value = response.data.filePath - khhaoStateFilePath.value = response.data.stateFilePath - khhaoAutoSyncState.value = response.data.syncState - showSuccess('khhao 来源配置已保存') - } catch (error) { - khhaoResultError.value = error instanceof Error ? error.message : 'khhao 来源配置保存失败' - showError(khhaoResultError.value) - } finally { - khhaoSaving.value = false - } - } - - async function handleKhhaoTestLogin() { - if (!ensureKhhaoCredentials()) { - return - } - - khhaoTesting.value = true - khhaoResultError.value = '' - - try { - const response = await testAdminKhhaoLogin({ - ...buildKhhaoPayload(), - includeImageBase64: false, - }) - khhaoLoginResult.value = response.data - showSuccess('khhao 登录测试成功') - } catch (error) { - khhaoResultError.value = error instanceof Error ? error.message : 'khhao 登录测试失败' - showError(khhaoResultError.value) - } finally { - khhaoTesting.value = false - } - } - - async function handleKhhaoQueryOrders() { - if (!ensureKhhaoCredentials()) { - return - } - - khhaoQuerying.value = true - khhaoResultError.value = '' - - try { - const response = await queryAdminKhhaoOrders(buildKhhaoPayload()) - khhaoQueryResult.value = response.data - showSuccess(`khhao 订单查询成功,共返回 ${response.data.itemCount} 条`) - } catch (error) { - khhaoResultError.value = error instanceof Error ? error.message : 'khhao 订单查询失败' - showError(khhaoResultError.value) - } finally { - khhaoQuerying.value = false - } - } - - async function handleKhhaoSyncOrders() { - if (!ensureKhhaoCredentials()) { - return - } - - khhaoSyncing.value = true - khhaoResultError.value = '' - - try { - const response = await syncAdminKhhaoOrders(buildKhhaoPayload()) - khhaoSyncResult.value = response.data - showSuccess(`khhao 同步完成:拉取 ${response.data.fetchedCount} 条,成功 ${response.data.syncedCount} 条`) - } catch (error) { - khhaoResultError.value = error instanceof Error ? error.message : 'khhao 订单同步失败' - showError(khhaoResultError.value) - } finally { - khhaoSyncing.value = false - } - } - - return { - khhaoFilePath, - khhaoStateFilePath, - khhaoForm, - khhaoSaving, - khhaoTesting, - khhaoQuerying, - khhaoSyncing, - khhaoResultError, - khhaoLoginResult, - khhaoQueryResult, - khhaoSyncResult, - khhaoAutoSyncState, - khhaoStats, - hydrateKhhaoConfig, - handleKhhaoSaveSource, - handleKhhaoTestLogin, - handleKhhaoQueryOrders, - handleKhhaoSyncOrders, - } -} diff --git a/apps/frontend/src/services/admin/platform-config.ts b/apps/frontend/src/services/admin/platform-config.ts index 2ea90837..a1f509f6 100644 --- a/apps/frontend/src/services/admin/platform-config.ts +++ b/apps/frontend/src/services/admin/platform-config.ts @@ -11,11 +11,6 @@ import type { AdminCloudtentaclesValidateSessionResult, AdminFulfillmentBindingConfigItem, AdminFulfillmentLookupResult, - AdminKhhaoLoginTestResult, - AdminKhhaoOrderQueryResult, - AdminKhhaoOrderSyncResult, - AdminKhhaoSourceConfig, - AdminKhhaoSyncState, AdminNinetyoneOrderActionResult, AdminNinetyoneOrderListResult, AdminKuaishouCloudFulfillmentConfig, @@ -46,56 +41,6 @@ export function saveAdminAgisoShopConfigs(payload: { }>('/api/v1/admin/platform-config/agiso-shops', payload) } -export function fetchAdminKhhaoSourceConfig() { - return apiGet<{ - filePath: string - source: AdminKhhaoSourceConfig - stateFilePath: string - syncState: AdminKhhaoSyncState - }>('/api/v1/admin/platform-config/khhao-source') -} - -export function saveAdminKhhaoSourceConfig(payload: AdminKhhaoSourceConfig) { - return apiPost<{ - filePath: string - source: AdminKhhaoSourceConfig - stateFilePath: string - syncState: AdminKhhaoSyncState - }>('/api/v1/admin/platform-config/khhao-source', payload as unknown as Record) -} - -export function testAdminKhhaoLogin(payload: { - baseUrl?: string - username: string - password: string - maxCaptchaAttempts?: number - includeImageBase64?: boolean -}) { - return apiPost('/api/v1/admin/platform-config/khhao/test-login', payload) -} - -export function queryAdminKhhaoOrders(payload: { - baseUrl?: string - username: string - password: string - page?: number - limit?: number - maxCaptchaAttempts?: number -}) { - return apiPost('/api/v1/admin/platform-config/khhao/query-orders', payload) -} - -export function syncAdminKhhaoOrders(payload: { - baseUrl?: string - username: string - password: string - page?: number - limit?: number - maxCaptchaAttempts?: number -}) { - return apiPost('/api/v1/admin/platform-config/khhao/sync-orders', payload) -} - export function fetchAdminNinetyoneOrders(params: { page?: number pageSize?: number diff --git a/apps/frontend/src/types/admin.ts b/apps/frontend/src/types/admin.ts index 8e8617b5..ef4c1c65 100644 --- a/apps/frontend/src/types/admin.ts +++ b/apps/frontend/src/types/admin.ts @@ -80,37 +80,6 @@ export interface AdminAgisoObservedShopItem { configured: boolean } -export interface AdminKhhaoLoginTestResult { - baseUrl: string - username: string - loggedInAt: string - attempt: number - responseMessage: string - captcha: { - recognizedText: string - imageBase64: string - } - session: { - cookieKeys: string[] - cookieCount: number - cookieHeaderMasked: string - } -} - -export interface AdminKhhaoSourceConfig { - enabled: boolean - baseUrl: string - username: string - password: string - maxCaptchaAttempts: number - autoSync: { - enabled: boolean - intervalMinutes: number - pages: number - pageSize: number - } -} - export interface AdminKuaishouEticketSourceConfig { enabled: boolean baseUrl: string @@ -195,82 +164,6 @@ export interface AdminKuaishouEticketConsumeResult { raw: Record } -export interface AdminKhhaoSyncState { - syncFromCreatedAt: string - lastRunStartedAt: string - lastRunFinishedAt: string - lastRunStatus: string - lastErrorMessage: string - fetchedCount: number - syncedCount: number - ignoredCount: number - watchMode: string - watchOrders: Array<{ - platformOrderId: string - shopId: string - shopName: string - itemTitle: string - payStatus: string - detectedAt: string - lastSeenAt: string - }> -} - -export interface AdminKhhaoOrderPreview { - provider: string - platform: string - platformLabel: string - platformOrderId: string - shopId: string - kuaishouShopId: string - khhaoShopId: string - shopName: string - itemId: string - itemTitle: string - skuCode: string - quantity: number - totalAmountFen: number - status: string - statusLabel: string - orderCreatedAt: string - raw: Record -} - -export interface AdminKhhaoOrderQueryResult { - baseUrl: string - page: number - limit: number - total: number - itemCount: number - items: AdminKhhaoOrderPreview[] - rawItems: Record[] -} - -export interface AdminKhhaoOrderSyncItem { - platformOrderId: string - provider: string - platform: string - shopId: string - skuCode: string - itemTitle: string - ignored: boolean - ignoreReason: string - orderId: number | null - orderItemCount: number - taskCount: number -} - -export interface AdminKhhaoOrderSyncResult { - baseUrl: string - page: number - limit: number - total: number - fetchedCount: number - syncedCount: number - ignoredCount: number - results: AdminKhhaoOrderSyncItem[] -} - export interface AdminNinetyoneOrderItem { orderId: number orderNo: string @@ -389,7 +282,6 @@ export interface AdminFulfillmentBindingConfigItem { platform: string shopId: string shopName: string - khhaoShopId: string skuCode: string skuName: string profileKey: string @@ -411,7 +303,6 @@ export interface AdminKuaishouCloudFulfillmentItem { provider: string platform: string shopId: string - khhaoShopId: string internalSkuCode: string internalSkuName: string externalSkuCode: string diff --git a/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue b/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue index 07f80a71..65f07361 100644 --- a/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue +++ b/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue @@ -4,7 +4,6 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue' import { showSuccess } from '@/lib/feedback' import { fetchAdminFulfillmentBindingConfigs, - fetchAdminKuaishouEticketSourceConfig, lookupAdminFulfillmentBindingOrder, saveAdminFulfillmentBindingConfigs, } from '@/services/admin' @@ -12,7 +11,6 @@ import type { AdminFulfillmentBindingConfigItem, AdminFulfillmentLookupItem, AdminFulfillmentLookupResult, - AdminKuaishouEticketShopConfigItem, AdminObservedProductItem, } from '@/types/admin' import { hasAdminRole } from '@/utils/admin-auth' @@ -24,7 +22,6 @@ type EditableBinding = { platform: string shopId: string shopName: string - khhaoShopId: string skuCode: string skuName: string profileKey: string @@ -41,7 +38,6 @@ type SaveBindingPayload = { platform: string shopId: string shopName: string - khhaoShopId: string skuCode: string skuName: string profileKey: string @@ -57,7 +53,7 @@ type SaveBindingPayload = { } } -type ValidationField = 'skuCode' | 'match' | 'profileKey' | 'shopId' | 'khhaoShopId' +type ValidationField = 'skuCode' | 'match' | 'profileKey' | 'shopId' type ValidationState = { bindingId: string @@ -78,7 +74,6 @@ const errorMessage = ref('') const filePath = ref('') const bindings = ref([]) const observedProducts = ref([]) -const kuaishouShopOptions = ref([]) const lookupLoading = ref(false) const lookupErrorMessage = ref('') const lookupResult = ref(null) @@ -113,7 +108,6 @@ type ImportableProductCandidate = Pick< AdminObservedProductItem, 'provider' | 'platform' | 'shopId' | 'shopName' | 'externalSkuCode' | 'externalItemId' | 'externalSkuName' > & { - khhaoShopId?: string } function createEmptyBinding(): EditableBinding { @@ -123,7 +117,6 @@ function createEmptyBinding(): EditableBinding { platform: '', shopId: '', shopName: '', - khhaoShopId: '', skuCode: '', skuName: '', profileKey: 'manual_review', @@ -137,14 +130,12 @@ function createEmptyBinding(): EditableBinding { } function mapEditableBinding(item: AdminFulfillmentBindingConfigItem): EditableBinding { - const matchedShop = resolveKuaishouShopById(item.shopId) || resolveKuaishouShopByName(item.shopName || '') - const binding = { + return { id: crypto.randomUUID(), provider: item.provider || 'agiso', platform: item.platform, - shopId: matchedShop?.shopId || item.shopId, - shopName: item.shopName || matchedShop?.kshopName || '', - khhaoShopId: item.khhaoShopId || '', + shopId: item.shopId, + shopName: item.shopName || '', skuCode: item.skuCode, skuName: item.skuName, profileKey: item.profileKey || 'manual_review', @@ -155,9 +146,6 @@ function mapEditableBinding(item: AdminFulfillmentBindingConfigItem): EditableBi externalSkuName: item.match.externalSkuName, resolvedSkuName: String(item.match.config?.resolvedSkuName || ''), } - - syncBindingShopSelection(binding) - return binding } function hasMatchCondition(item: Pick) { @@ -205,64 +193,16 @@ function getBindingStatusLabel(binding: EditableBinding) { } function getBindingSummary(binding: EditableBinding) { - const shopLabel = isKhhaoKuaishouBinding(binding) - ? [ - binding.shopName.trim() || binding.shopId.trim() || '未选快手店铺', - binding.khhaoShopId.trim() ? `khhao#${binding.khhaoShopId.trim()}` : '', - ].filter(Boolean).join(' / ') - : (binding.shopName.trim() || binding.shopId.trim() || '跨店铺') const parts = [ binding.provider.trim() || 'agiso', binding.platform.trim() || '未选平台', - shopLabel, + binding.shopName.trim() || binding.shopId.trim() || '跨店铺', binding.profileKey.trim() || 'manual_review', ] return parts.join(' / ') } -function isKhhaoKuaishouBinding(binding: Pick) { - return binding.provider.trim() === 'khhao' && binding.platform.trim() === 'kuaishou' -} - -function resolveKuaishouShopById(shopId: string) { - const normalizedShopId = shopId.trim() - return kuaishouShopOptions.value.find((item) => item.shopId === normalizedShopId) || null -} - -function resolveKuaishouShopByName(shopName: string) { - const normalizedShopName = shopName.trim() - return kuaishouShopOptions.value.find((item) => item.kshopName === normalizedShopName) || null -} - -function getKuaishouShopOptionLabel(item: AdminKuaishouEticketShopConfigItem) { - return item.kshopName.trim() - ? `${item.kshopName} (${item.shopId})` - : item.shopId -} - -function syncBindingShopSelection(binding: EditableBinding) { - if (!isKhhaoKuaishouBinding(binding)) { - return - } - - const matchedById = resolveKuaishouShopById(binding.shopId) - if (matchedById) { - binding.shopId = matchedById.shopId - binding.shopName = matchedById.kshopName - return - } - - const matchedByName = resolveKuaishouShopByName(binding.shopName) - if (matchedByName) { - binding.shopId = matchedByName.shopId - binding.shopName = matchedByName.kshopName - return - } - - binding.shopId = '' -} - async function loadConfigs() { if (!hasAdminRole('admin')) { loading.value = false @@ -273,12 +213,8 @@ async function loadConfigs() { errorMessage.value = '' try { - const [response, kuaishouSourceResponse] = await Promise.all([ - fetchAdminFulfillmentBindingConfigs(), - fetchAdminKuaishouEticketSourceConfig(), - ]) + const response = await fetchAdminFulfillmentBindingConfigs() filePath.value = response.data.filePath - kuaishouShopOptions.value = kuaishouSourceResponse.data.source.shops.filter((item) => item.enabled !== false) const nextBindings = response.data.bindings.map(mapEditableBinding) bindings.value = nextBindings collapsedBindingIds.value = buildCollapsedIds(nextBindings) @@ -312,7 +248,6 @@ function createBindingFromProductCandidate(item: ImportableProductCandidate): Ed platform: item.platform, shopId: item.shopId, shopName: item.shopName, - khhaoShopId: item.khhaoShopId || '', skuCode: '', skuName: item.externalSkuName, profileKey: 'manual_review', @@ -324,7 +259,6 @@ function createBindingFromProductCandidate(item: ImportableProductCandidate): Ed resolvedSkuName: item.externalSkuName, } - syncBindingShopSelection(binding) return binding } @@ -347,14 +281,11 @@ function isLookupProductImported(lineId: string) { } function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload { - syncBindingShopSelection(item) - return { provider: item.provider.trim() || 'agiso', platform: item.platform.trim(), shopId: item.shopId.trim(), shopName: item.shopName.trim(), - khhaoShopId: item.khhaoShopId.trim(), skuCode: item.skuCode.trim(), skuName: item.skuName.trim(), profileKey: item.profileKey.trim() || 'manual_review', @@ -376,7 +307,6 @@ function hasBindingContent(item: SaveBindingPayload) { item.platform || item.shopId || item.shopName - || item.khhaoShopId || item.skuCode || item.skuName || item.match.externalSkuCode @@ -392,24 +322,6 @@ function hasBindingContent(item: SaveBindingPayload) { function resolveBindingValidationState(item: SaveBindingPayload, index: number, bindingId: string): ValidationState { const label = `第 ${index + 1} 条规则` - const provider = item.provider.trim() || 'agiso' - const platform = item.platform.trim() - - if (provider === 'khhao' && platform === 'kuaishou' && (!item.shopId || !item.shopName)) { - return { - bindingId, - field: 'shopId', - message: `${label} 需要选择已配置的快手官方店铺`, - } - } - - if (provider === 'khhao' && platform === 'kuaishou' && !item.khhaoShopId) { - return { - bindingId, - field: 'khhaoShopId', - message: `${label} 需要填写 khhao 店铺 ID`, - } - } if (!item.skuCode) { return { @@ -454,7 +366,6 @@ async function focusValidationTarget(state: ValidationState) { card.scrollIntoView({ behavior: 'smooth', block: 'center' }) const selectors: Record = { - khhaoShopId: '[data-field="khhaoShopId"]', shopId: '[data-field="shopId"]', skuCode: '[data-field="skuCode"]', match: '[data-field="externalSkuCode"]', @@ -485,16 +396,6 @@ function isFieldInvalid(bindingId: string, field: ValidationField | 'externalSku return validationState.value.field === field } -function handleBindingProviderPlatformChange(binding: EditableBinding) { - syncBindingShopSelection(binding) - clearValidationState() -} - -function handleKuaishouShopChange(binding: EditableBinding) { - syncBindingShopSelection(binding) - clearValidationState() -} - async function saveConfigs() { const normalizedBindings = bindings.value.map(normalizeBindingForSave) const nonEmptyBindings = normalizedBindings.filter(hasBindingContent) @@ -694,7 +595,7 @@ onMounted(loadConfigs) v-model="binding.provider" class="text-input" placeholder="agiso" - @input="handleBindingProviderPlatformChange(binding)" + @input="clearValidationState" /> @@ -704,76 +605,30 @@ onMounted(loadConfigs) v-model="binding.platform" class="text-input" placeholder="xianyu / taobao / pdd" - @input="handleBindingProviderPlatformChange(binding)" + @input="clearValidationState" /> - - - +