实现内置虎牙扫码登录与绑定 Connector
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { isDevMockEnabled } from './dev-mock-enabled.js'
|
||||
|
||||
export const DEV_MOCK_CONNECTOR_PATH = '/api/v1/dev-mock/connector'
|
||||
export const DEV_MOCK_CONNECTOR_API_KEY = 'local-semi-auto-connector-key'
|
||||
|
||||
export function devMockConnectorBaseUrl() {
|
||||
const port = Math.trunc(Number(runtimeConfig.server.port || 3000))
|
||||
return `http://127.0.0.1:${port}${DEV_MOCK_CONNECTOR_PATH}`
|
||||
}
|
||||
|
||||
export function assertDevMockConnectorEnabled() {
|
||||
if (!isDevMockEnabled()) {
|
||||
throw createHttpError('内置 Mock Connector 仅在开发环境可用', {
|
||||
statusCode: 503,
|
||||
errorCode: 'dev_mock_connector_disabled',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { isProductionLike } from '../../config/runtime-validation.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
|
||||
export function isDevMockEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
const flag = String(env.ENABLE_DEV_MOCK || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (flag === '1' || flag === 'true') {
|
||||
return true
|
||||
}
|
||||
return !isProductionLike(env)
|
||||
}
|
||||
|
||||
export function assertDevMockEnabled() {
|
||||
if (!isDevMockEnabled()) {
|
||||
throw createHttpError('当前环境未开启开发 Mock(仅非 production 或 ENABLE_DEV_MOCK=1)', {
|
||||
statusCode: 403,
|
||||
errorCode: 'dev_mock_disabled',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,19 @@ import {
|
||||
getKuaishouIndustrySourceConfig,
|
||||
listKuaishouIndustryShopConfigs,
|
||||
} from '../platforms/kuaishou-industry/source-config-service.js'
|
||||
import { isProductionLike } from '../../config/runtime-validation.js'
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { randomId } from '../../utils/random.js'
|
||||
import { TASK_STATUS } from '../../domain/task-status.js'
|
||||
import { ensureSemiAutoTasksForOrder } from '../semi-auto/semi-auto-task-service.js'
|
||||
import {
|
||||
defaultSemiAutoConnectorMode,
|
||||
isExternalSemiAutoConnectorConfigured,
|
||||
type SemiAutoConnectorMode,
|
||||
} from '../semi-auto/huya-connector-client.js'
|
||||
import { assertDevMockEnabled, isDevMockEnabled } from './dev-mock-enabled.js'
|
||||
|
||||
export { assertDevMockEnabled, isDevMockEnabled }
|
||||
|
||||
export type DevMockCreateResult = {
|
||||
platform: 'affiliate_dash'
|
||||
@@ -69,35 +75,13 @@ export type DevMockSemiAutoResult = {
|
||||
configId: number
|
||||
taskId: number
|
||||
skuKey: string
|
||||
connectorMode: 'mock' | 'external'
|
||||
connectorMode: SemiAutoConnectorMode
|
||||
connectorConfigured: boolean
|
||||
customerPath: string
|
||||
adminPath: string
|
||||
tips: string[]
|
||||
}
|
||||
|
||||
export function isDevMockEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
if (String(env.ENABLE_DEV_MOCK || '').trim() === '1') {
|
||||
return true
|
||||
}
|
||||
if (
|
||||
String(env.ENABLE_DEV_MOCK || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'true'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return !isProductionLike(env)
|
||||
}
|
||||
|
||||
export function assertDevMockEnabled() {
|
||||
if (!isDevMockEnabled()) {
|
||||
throw createHttpError('当前环境未开启开发 Mock(仅非 production 或 ENABLE_DEV_MOCK=1)', {
|
||||
statusCode: 403,
|
||||
errorCode: 'dev_mock_disabled',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function getDevMockStatus() {
|
||||
const enabled = isDevMockEnabled()
|
||||
return {
|
||||
@@ -115,28 +99,21 @@ export function getDevMockStatus() {
|
||||
key: 'semi_auto',
|
||||
name: '半自动履约',
|
||||
description:
|
||||
'创建人工渠道半自动测试单;登录、绑定和浏览器票据由内置 Mock Connector 模拟',
|
||||
'创建人工渠道半自动测试单;可选择仅 Mock 订单,登录、绑定和票据注入走真实 Connector',
|
||||
steps: ['create_order', 'login', 'binding', 'browser_ticket', 'complete'],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
defaultUid: '10001',
|
||||
semiAuto: {
|
||||
connectorMode: semiAutoConnectorMode(),
|
||||
connectorConfigured: Boolean(
|
||||
String(runtimeConfig.semiAuto?.connectorBaseUrl || '').trim() &&
|
||||
String(runtimeConfig.semiAuto?.connectorApiKey || '').trim(),
|
||||
),
|
||||
defaultConnectorMode: defaultSemiAutoConnectorMode(),
|
||||
externalConnectorConfigured: isExternalSemiAutoConnectorConfigured(),
|
||||
mockConnectorAvailable: enabled,
|
||||
platform: 'huya',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function semiAutoConnectorMode(): 'mock' | 'external' {
|
||||
const baseUrl = String(runtimeConfig.semiAuto?.connectorBaseUrl || '').trim()
|
||||
return baseUrl.includes('/dev-mock/connector') ? 'mock' : 'external'
|
||||
}
|
||||
|
||||
type AffiliateDashMockStep = 'uid' | 'bind' | 'submitted' | 'completed' | 'failed'
|
||||
|
||||
/**
|
||||
@@ -329,7 +306,7 @@ export async function createAffiliateDashMockClaim(
|
||||
|
||||
/**
|
||||
* 生成半自动履约测试单:人工 SKU 配置、订单、履约和半自动任务都走真实领域服务。
|
||||
* 平台登录、绑定和浏览器票据由内置 Mock Connector 模拟。
|
||||
* 平台登录、绑定和浏览器票据按 connectorMode 选择真实 Connector 或内置 Mock。
|
||||
*/
|
||||
export async function createSemiAutoMock(
|
||||
input: {
|
||||
@@ -339,6 +316,7 @@ export async function createSemiAutoMock(
|
||||
pageTitle?: unknown
|
||||
pageDescription?: unknown
|
||||
browserTargetUrl?: unknown
|
||||
connectorMode?: unknown
|
||||
} = {},
|
||||
): Promise<DevMockSemiAutoResult> {
|
||||
assertDevMockEnabled()
|
||||
@@ -346,6 +324,7 @@ export async function createSemiAutoMock(
|
||||
const orderNo = String(input.orderNo || `MOCKSA${Date.now()}`).trim()
|
||||
const skuKey = normalizeMockSemiAutoSku(input.skuName)
|
||||
const quantity = normalizeMockSemiAutoQuantity(input.quantity)
|
||||
const connectorMode = normalizeMockSemiAutoConnectorMode(input.connectorMode)
|
||||
const pageTitle = String(input.pageTitle || '虎牙账号授权测试')
|
||||
.trim()
|
||||
.slice(0, 60)
|
||||
@@ -370,8 +349,14 @@ export async function createSemiAutoMock(
|
||||
semiAutoConfig: {
|
||||
pageTitle,
|
||||
pageDescription,
|
||||
loginInstructions: 'Mock 登录:点击获取二维码后,页面轮询两次会自动模拟登录成功。',
|
||||
bindingInstructions: 'Mock 绑定:页面轮询两次后会自动识别角色,点击确认即可。',
|
||||
loginInstructions:
|
||||
connectorMode === 'mock'
|
||||
? 'Mock 登录:点击获取二维码后,页面轮询两次会自动模拟登录成功。'
|
||||
: '真实登录:点击获取二维码后,请使用虎牙 App 扫码并在手机上确认登录。',
|
||||
bindingInstructions:
|
||||
connectorMode === 'mock'
|
||||
? 'Mock 绑定:页面轮询两次后会自动识别角色,点击确认即可。'
|
||||
: '真实绑定:扫码后等待 Connector 识别角色,核对账号和区服后点击确认绑定。',
|
||||
browserTargets: [{ key: 'huya', label: '虎牙', url: browserTargetUrl }],
|
||||
},
|
||||
})
|
||||
@@ -469,7 +454,13 @@ export async function createSemiAutoMock(
|
||||
})
|
||||
}
|
||||
|
||||
const connectorMode = semiAutoConnectorMode()
|
||||
await query('UPDATE semi_auto_tasks SET connector_mode = $2, updated_at = NOW() WHERE id = $1', [
|
||||
taskId,
|
||||
connectorMode,
|
||||
])
|
||||
|
||||
const connectorConfigured =
|
||||
connectorMode === 'external' ? isExternalSemiAutoConnectorConfigured() : true
|
||||
return {
|
||||
platform: 'semi_auto',
|
||||
orderId: Number(order.id),
|
||||
@@ -480,20 +471,35 @@ export async function createSemiAutoMock(
|
||||
taskId,
|
||||
skuKey,
|
||||
connectorMode,
|
||||
connectorConfigured,
|
||||
customerPath: `/c/${encodeURIComponent(orderNo)}`,
|
||||
adminPath: `/admin/semi-auto-tasks?keyword=${encodeURIComponent(orderNo)}`,
|
||||
tips: [
|
||||
'打开客户页,点击“获取登录二维码”;Mock Connector 会在两次轮询后自动模拟登录成功。',
|
||||
'登录后页面会自动创建绑定二维码,轮询两次后识别角色,点击“确认绑定角色”。',
|
||||
'后台“任务 → 半自动履约”领取任务后生成浏览器票据;扩展消费票据后写入 Mock Cookie。',
|
||||
'完成履约仍走真实确认流程,会写入成本快照和财务确认。',
|
||||
connectorMode === 'mock'
|
||||
? '当前使用内置 Mock Connector,不需要启动 live-hub-py。'
|
||||
: '当前配置的是外部 Connector;请确认 Connector 已启动,否则登录和绑定会失败。',
|
||||
? '当前使用内置 Mock Connector:打开客户页点击登录,轮询两次后自动登录;绑定同样自动识别角色。'
|
||||
: connectorMode === 'external'
|
||||
? connectorConfigured
|
||||
? '当前使用远程真实 Connector:订单是 Mock 的,登录、绑定和浏览器票据都走外部服务。'
|
||||
: '当前选择远程真实 Connector,但 SEMI_AUTO_CONNECTOR_BASE_URL / API_KEY 未配置,登录会失败。'
|
||||
: '当前使用 order_site 内置真实 Connector:无需启动 live-hub-py,扫码登录、绑定和票据都在本进程完成。',
|
||||
connectorMode === 'mock'
|
||||
? '后台领取任务后生成浏览器票据;扩展消费票据后写入 Mock Cookie。'
|
||||
: connectorMode === 'external'
|
||||
? '请确保远程 Connector 已启动,并保证两边 SEMI_AUTO_CONNECTOR_API_KEY 一致。'
|
||||
: '登录凭据只保存在当前进程内存中,重启后端会要求客户重新扫码。',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMockSemiAutoConnectorMode(value: unknown): SemiAutoConnectorMode {
|
||||
const mode = String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (mode === 'external' || mode === 'mock') return mode
|
||||
return 'embedded'
|
||||
}
|
||||
|
||||
function normalizeMockSemiAutoSku(value: unknown) {
|
||||
const raw = String(value || '').trim()
|
||||
const withCjk = raw
|
||||
|
||||
Reference in New Issue
Block a user