后端迁移后台管理快手Cloud写侧
This commit is contained in:
+23
-23
@@ -1,5 +1,3 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
import { getOrderById } from '../../../repositories/order-repo.js'
|
import { getOrderById } from '../../../repositories/order-repo.js'
|
||||||
import { updateTask } from '../../../repositories/task-repo.js'
|
import { updateTask } from '../../../repositories/task-repo.js'
|
||||||
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
||||||
@@ -46,15 +44,17 @@ import {
|
|||||||
resolvePersistedCloudtentaclesContext,
|
resolvePersistedCloudtentaclesContext,
|
||||||
} from './kuaishou-cloud-helpers.js'
|
} from './kuaishou-cloud-helpers.js'
|
||||||
|
|
||||||
/** @typedef {import('../../../types/admin-read-inputs.js').AdminEntityIdInput} AdminEntityIdInput */
|
import type {
|
||||||
/** @typedef {import('../../../types/admin-read-inputs.js').AdminViewerSessionInput} AdminViewerSessionInput */
|
AdminEntityIdInput,
|
||||||
/** @typedef {import('../../../types/admin-write-inputs.js').AdminTaskKuaishouCloudDispatchInput} AdminTaskKuaishouCloudDispatchInput */
|
AdminViewerSessionInput,
|
||||||
/** @typedef {import('../../../types/admin-write-models.js').AdminTaskActionResponse} AdminTaskActionResponse */
|
} from '../../../types/admin-read-inputs.js'
|
||||||
|
import type { AdminTaskKuaishouCloudDispatchInput } from '../../../types/admin-write-inputs.js'
|
||||||
|
import type { AdminTaskActionResponse } from '../../../types/admin-write-models.js'
|
||||||
|
|
||||||
/** @returns {Promise<AdminTaskActionResponse>} */
|
export async function prepareAdminTaskKuaishouCloudFulfillment(
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
taskId: AdminEntityIdInput,
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
session: AdminViewerSessionInput | null = null,
|
||||||
export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session = null) {
|
): Promise<AdminTaskActionResponse> {
|
||||||
const task = await getRequiredTask(taskId)
|
const task = await getRequiredTask(taskId)
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const viewerContext = createAdminViewerContext(session)
|
const viewerContext = createAdminViewerContext(session)
|
||||||
@@ -226,11 +226,11 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<AdminTaskActionResponse>} */
|
export async function dispatchAdminTaskKuaishouCloudFulfillment(
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
taskId: AdminEntityIdInput,
|
||||||
/** @param {AdminTaskKuaishouCloudDispatchInput} [payload] */
|
payload: AdminTaskKuaishouCloudDispatchInput = {},
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
session: AdminViewerSessionInput | null = null,
|
||||||
export async function dispatchAdminTaskKuaishouCloudFulfillment(taskId, payload = {}, session = null) {
|
): Promise<AdminTaskActionResponse> {
|
||||||
const task = await getRequiredTask(taskId)
|
const task = await getRequiredTask(taskId)
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const viewerContext = createAdminViewerContext(session)
|
const viewerContext = createAdminViewerContext(session)
|
||||||
@@ -334,10 +334,10 @@ export async function dispatchAdminTaskKuaishouCloudFulfillment(taskId, payload
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<AdminTaskActionResponse>} */
|
export async function refreshAdminTaskKuaishouCloudRoleInfo(
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
taskId: AdminEntityIdInput,
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
session: AdminViewerSessionInput | null = null,
|
||||||
export async function refreshAdminTaskKuaishouCloudRoleInfo(taskId, session = null) {
|
): Promise<AdminTaskActionResponse> {
|
||||||
const task = await getRequiredTask(taskId)
|
const task = await getRequiredTask(taskId)
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const viewerContext = createAdminViewerContext(session)
|
const viewerContext = createAdminViewerContext(session)
|
||||||
@@ -418,10 +418,10 @@ export async function refreshAdminTaskKuaishouCloudRoleInfo(taskId, session = nu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<AdminTaskActionResponse>} */
|
export async function returnNumberAdminTaskKuaishouCloudFulfillment(
|
||||||
/** @param {AdminEntityIdInput} taskId */
|
taskId: AdminEntityIdInput,
|
||||||
/** @param {AdminViewerSessionInput | null} [session] */
|
session: AdminViewerSessionInput | null = null,
|
||||||
export async function returnNumberAdminTaskKuaishouCloudFulfillment(taskId, session = null) {
|
): Promise<AdminTaskActionResponse> {
|
||||||
const task = await getRequiredTask(taskId)
|
const task = await getRequiredTask(taskId)
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const viewerContext = createAdminViewerContext(session)
|
const viewerContext = createAdminViewerContext(session)
|
||||||
+71
-25
@@ -1,5 +1,3 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
import { normalizeProductName } from '../../order/product-match-service.js'
|
import { normalizeProductName } from '../../order/product-match-service.js'
|
||||||
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
import { getCloudtentaclesSourceConfig } from '../../platforms/cloudtentacles/source-config-service.js'
|
||||||
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
import { getCloudtentaclesSessionState } from '../../platforms/cloudtentacles/session-state-service.js'
|
||||||
@@ -16,11 +14,58 @@ import { createHttpError } from '../../../utils/http.js'
|
|||||||
|
|
||||||
export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
||||||
|
|
||||||
export function isKuaishouCloudTask(task) {
|
export type JsonObject = Record<string, any>
|
||||||
return String(task?.executor_key || '').trim() === 'kuaishou_ct_assisted'
|
|
||||||
|
export type CloudtentaclesContext = {
|
||||||
|
baseUrl: string
|
||||||
|
token: string
|
||||||
|
deviceId: string
|
||||||
|
deviceType: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolvePersistedCloudtentaclesContext() {
|
export type CloudSkuLikeItem = {
|
||||||
|
id?: number | string
|
||||||
|
name?: string
|
||||||
|
price?: number | string
|
||||||
|
count?: number | string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KuaishouCloudFlow = JsonObject & {
|
||||||
|
configId: string
|
||||||
|
internalSkuCode: string
|
||||||
|
internalSkuName: string
|
||||||
|
ticket: JsonObject
|
||||||
|
binding: JsonObject
|
||||||
|
role: JsonObject
|
||||||
|
purchase: JsonObject
|
||||||
|
dispatch: JsonObject
|
||||||
|
returnNumber: JsonObject
|
||||||
|
consume: JsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KuaishouCloudBindingResources = {
|
||||||
|
skuId: number
|
||||||
|
skuName: string
|
||||||
|
vnKey: string
|
||||||
|
skuItem: CloudSkuLikeItem | null
|
||||||
|
knapsackItem: CloudSkuLikeItem | null
|
||||||
|
resolvedByName: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PreparedKuaishouCloudBindResource = {
|
||||||
|
vnKey: string
|
||||||
|
vnId: number
|
||||||
|
vnPhone: string
|
||||||
|
bindUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isKuaishouCloudTask(task: unknown): boolean {
|
||||||
|
const record: JsonObject = task && typeof task === 'object' ? task : {}
|
||||||
|
return String(record.executor_key || '').trim() === 'kuaishou_ct_assisted'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolvePersistedCloudtentaclesContext(): CloudtentaclesContext {
|
||||||
const source = getCloudtentaclesSourceConfig()
|
const source = getCloudtentaclesSourceConfig()
|
||||||
const session = getCloudtentaclesSessionState()
|
const session = getCloudtentaclesSessionState()
|
||||||
const token = String(session.token || '').trim()
|
const token = String(session.token || '').trim()
|
||||||
@@ -40,8 +85,8 @@ export function resolvePersistedCloudtentaclesContext() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeKuaishouCloudFlow(value) {
|
export function normalizeKuaishouCloudFlow(value: unknown): KuaishouCloudFlow {
|
||||||
const source = value && typeof value === 'object' ? value : {}
|
const source: JsonObject = value && typeof value === 'object' ? value : {}
|
||||||
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
const binding = source.binding && typeof source.binding === 'object' ? source.binding : {}
|
||||||
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
const role = source.role && typeof source.role === 'object' ? source.role : {}
|
||||||
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {}
|
||||||
@@ -125,8 +170,8 @@ export function normalizeKuaishouCloudFlow(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeKuaishouCloudRoleInfo(value) {
|
export function normalizeKuaishouCloudRoleInfo(value: unknown): { name: string, rid: string, rawInfo: JsonObject | null } {
|
||||||
const rawInfo = value && typeof value === 'object' ? value : null
|
const rawInfo: JsonObject | null = value && typeof value === 'object' ? value : null
|
||||||
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
const nestedBindInfo = rawInfo?.sBindInfo && typeof rawInfo.sBindInfo === 'object' ? rawInfo.sBindInfo : null
|
||||||
const source = nestedBindInfo || rawInfo
|
const source = nestedBindInfo || rawInfo
|
||||||
|
|
||||||
@@ -137,7 +182,7 @@ export function normalizeKuaishouCloudRoleInfo(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt: unknown): string | null {
|
||||||
const preparedTime = Date.parse(String(preparedAt || ''))
|
const preparedTime = Date.parse(String(preparedAt || ''))
|
||||||
if (!Number.isFinite(preparedTime)) {
|
if (!Number.isFinite(preparedTime)) {
|
||||||
return null
|
return null
|
||||||
@@ -148,7 +193,10 @@ export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
|
|||||||
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
|
export function resolveKuaishouCloudBindingResources(
|
||||||
|
flow: KuaishouCloudFlow,
|
||||||
|
{ skuItems = [], knapsackItems = [] }: { skuItems?: unknown[], knapsackItems?: unknown[] } = {},
|
||||||
|
): KuaishouCloudBindingResources {
|
||||||
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
|
||||||
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
|
||||||
const currentSkuId = Number(flow?.binding?.skuId || 0) || 0
|
const currentSkuId = Number(flow?.binding?.skuId || 0) || 0
|
||||||
@@ -192,20 +240,17 @@ export function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function resolveKuaishouCloudVnKeyCandidates(input: { flow?: KuaishouCloudFlow, binding?: KuaishouCloudBindingResources } = {}): string[] {
|
||||||
* @param {{ flow?: any, binding?: any }} [input]
|
|
||||||
*/
|
|
||||||
export function resolveKuaishouCloudVnKeyCandidates(input = {}) {
|
|
||||||
return [KUAISHOU_CLOUD_FIXED_VN_KEY]
|
return [KUAISHOU_CLOUD_FIXED_VN_KEY]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function prepareKuaishouCloudBindResourceWithFallback(input: {
|
||||||
* @param {{ cloudContext?: Record<string, unknown>, vnKeyCandidates?: string[] }} [input]
|
cloudContext?: CloudtentaclesContext
|
||||||
*/
|
vnKeyCandidates?: string[]
|
||||||
export async function prepareKuaishouCloudBindResourceWithFallback(input = {}) {
|
} = {}): Promise<PreparedKuaishouCloudBindResource> {
|
||||||
const { cloudContext = {}, vnKeyCandidates = [] } = input
|
const { cloudContext = {}, vnKeyCandidates = [] } = input
|
||||||
const candidates = Array.isArray(vnKeyCandidates) ? vnKeyCandidates : []
|
const candidates = Array.isArray(vnKeyCandidates) ? vnKeyCandidates : []
|
||||||
let lastError = null
|
let lastError: unknown = null
|
||||||
|
|
||||||
for (const vnKey of candidates) {
|
for (const vnKey of candidates) {
|
||||||
let vnId = 0
|
let vnId = 0
|
||||||
@@ -284,7 +329,7 @@ export async function prepareKuaishouCloudBindResourceWithFallback(input = {}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectKuaishouCloudNameCandidates(flow) {
|
function collectKuaishouCloudNameCandidates(flow: KuaishouCloudFlow): string[] {
|
||||||
return Array.from(new Set([
|
return Array.from(new Set([
|
||||||
String(flow?.binding?.skuName || '').trim(),
|
String(flow?.binding?.skuName || '').trim(),
|
||||||
String(flow?.internalSkuName || '').trim(),
|
String(flow?.internalSkuName || '').trim(),
|
||||||
@@ -292,7 +337,7 @@ function collectKuaishouCloudNameCandidates(flow) {
|
|||||||
].filter(Boolean)))
|
].filter(Boolean)))
|
||||||
}
|
}
|
||||||
|
|
||||||
function findCloudItemByNames(items, nameCandidates, preferredId = 0) {
|
function findCloudItemByNames(items: CloudSkuLikeItem[], nameCandidates: string[], preferredId = 0): CloudSkuLikeItem | null {
|
||||||
const normalizedItems = Array.isArray(items) ? items : []
|
const normalizedItems = Array.isArray(items) ? items : []
|
||||||
const normalizedNames = nameCandidates
|
const normalizedNames = nameCandidates
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
@@ -333,11 +378,12 @@ function findCloudItemByNames(items, nameCandidates, preferredId = 0) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCloudSkuLikeItem(item) {
|
function isCloudSkuLikeItem(item: unknown): item is CloudSkuLikeItem {
|
||||||
return Boolean(item) && typeof item === 'object' && Number(item.id || 0) > 0
|
const record: JsonObject = item && typeof item === 'object' ? item : {}
|
||||||
|
return Number(record.id || 0) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRecoverableKuaishouCloudVnKeyError(error) {
|
function isRecoverableKuaishouCloudVnKeyError(error: any): boolean {
|
||||||
const errorCode = String(error?.errorCode || error?.code || '').trim()
|
const errorCode = String(error?.errorCode || error?.code || '').trim()
|
||||||
const errorMessage = String(error?.message || '').trim()
|
const errorMessage = String(error?.message || '').trim()
|
||||||
return errorCode === 'cloudtentacles_vn_bind_url_failed'
|
return errorCode === 'cloudtentacles_vn_bind_url_failed'
|
||||||
@@ -776,6 +776,15 @@
|
|||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
- `npm test` 共 139 个用例通过
|
- `npm test` 共 139 个用例通过
|
||||||
|
198. 后台 Admin 快手 Cloud 写侧 helper / actions 迁移到 `.ts`:
|
||||||
|
- `src/services/admin/write/kuaishou-cloud-helpers.ts`
|
||||||
|
- `src/services/admin/write/kuaishou-cloud-actions.ts`
|
||||||
|
199. 快手 Cloud flow 规范化、SKU / 背包资源匹配、VN Key 候选、绑定资源准备与回滚、准备绑定、发货、刷新角色、退号和快手核销动作已进入 TS 编译链路;cloud context、flow、SKU-like item、binding resources、prepared bind resource、dispatch input 与 action response 补齐类型
|
||||||
|
200. Docker 内验证通过:
|
||||||
|
- `src/services/admin/*.test.js` 共 8 个用例通过
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run build`
|
||||||
|
- `npm test` 共 139 个用例通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user