feat(pdd): 原生内嵌拼多多多店铺订单同步子服务与anti-content纯算引擎

1. 纯算引擎 (pdd-anti-content.ts):
   - 100% 纯 TypeScript 原生还原 0as anti-content 算法,零 npm 依赖(仅基于 Node.js 原生 zlib 与 crypto)。
   - 包含 22 个环境特征 Tag TLV 组包、zlib 标准压缩、4 字节尾部扰动盐与 64 字符动态位移 Base64 编码,单次耗时 < 0.1ms。

2. MMS 接口客户端与数据映射 (pdd-client.ts, pdd-order-mapper.ts):
   - 基于原生 fetch 实现高可用 MMS 请求,支持拼多多网关毫秒级服务器时间校准 (/api/server/_stm)。
   - 实现拼多多订单状态与字段规范映射为平台 SourceOrderEvent,精准保留买家留言与充值账号信息。

3. 多店铺配置与防风控错峰调度 (pdd-source-config-service.ts, pdd-sync-service.ts):
   - 支持多店铺账号配置持久化(PostgreSQL app_config_entries 与本地 JSON 双模兜底)。
   - 实现店铺间轻量错峰拉取机制,避免并发峰值触发平台 40002 频控。

4. 内存级自动进单与派单流转:
   - 拉取新订单后在 Node.js 内存中直接调用 upsertOrderFromSource(),自动完成 orders/order_items 入库、商品规则匹配与打手大厅工单 (work_orders) 实时广播。

5. 调度器与管理端 API (scheduler-service.ts, platform-config/pdd.ts):
   - 注册 pdd_order_sync 定时任务(默认 20s 轮询),提供管理端店铺配置查看、保存、删除及手动立即同步接口。
   - 所有新增代码均遵循中英双语注释规范,并通过类型检查与单元测试。
This commit is contained in:
yml2213
2026-09-12 14:12:11 +08:00
parent 1b6e117c7f
commit 424c320183
16 changed files with 1452 additions and 2 deletions
@@ -10,6 +10,7 @@ const CLOUDTENTACLES_HEALTH_JOB_ID = 'cloudtentacles-health'
const WORK_ORDER_TIMEOUT_JOB_ID = 'work-order-timeout'
const DEPOSIT_UNFREEZE_JOB_ID = 'deposit-unfreeze'
const DATA_RETENTION_JOB_ID = 'data-retention'
const PDD_ORDER_SYNC_JOB_ID = 'pdd_order_sync'
export function getScheduledJobsFilePath() {
return SCHEDULED_JOBS_FILE_PATH
@@ -57,6 +58,14 @@ export function getDepositUnfreezeJob(config: JsonObject = getScheduledJobsConfi
)
}
export function getPddOrderSyncJob(config: JsonObject = getScheduledJobsConfig()) {
return (
(Array.isArray(config.jobs) ? config.jobs : []).find(
(item) => String(item.id || '').trim() === PDD_ORDER_SYNC_JOB_ID,
) || createDefaultPddOrderSyncJob()
)
}
export function getScheduledJobById(jobId: unknown, config: JsonObject = getScheduledJobsConfig()) {
return (
(Array.isArray(config.jobs) ? config.jobs : []).find(
@@ -75,6 +84,7 @@ export function normalizeScheduledJobsConfig(rawValue: unknown) {
const hasWorkOrderTimeout = jobs.some((item) => item.id === WORK_ORDER_TIMEOUT_JOB_ID)
const hasDepositUnfreeze = jobs.some((item) => item.id === DEPOSIT_UNFREEZE_JOB_ID)
const hasDataRetention = jobs.some((item) => item.id === DATA_RETENTION_JOB_ID)
const hasPddOrderSync = jobs.some((item) => item.id === PDD_ORDER_SYNC_JOB_ID)
if (!hasCloudtentaclesHealth) {
jobs.push(createDefaultCloudtentaclesHealthJob())
@@ -88,6 +98,9 @@ export function normalizeScheduledJobsConfig(rawValue: unknown) {
if (!hasDataRetention) {
jobs.push(createDefaultDataRetentionJob())
}
if (!hasPddOrderSync) {
jobs.push(createDefaultPddOrderSyncJob())
}
return {
enabled: typeof source.enabled === 'boolean' ? source.enabled : true,
@@ -113,10 +126,27 @@ function normalizeScheduledJob(rawValue: unknown) {
if (type === 'data_retention') {
return normalizeDataRetentionJob(rawValue)
}
if (type === 'pdd_order_sync') {
return normalizePddOrderSyncJob(rawValue)
}
return null
}
function normalizePddOrderSyncJob(rawValue: JsonObject) {
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
return {
id: PDD_ORDER_SYNC_JOB_ID,
type: 'pdd_order_sync',
enabled: rawValue.enabled !== false,
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 20, 10, 3600),
config: {
pageSize: normalizeRangeInteger(config.pageSize, 10, 1, 50),
},
}
}
function normalizeDepositUnfreezeJob(rawValue: JsonObject) {
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
@@ -230,10 +260,23 @@ function createDefaultScheduledJobsConfig() {
createDefaultWorkOrderTimeoutJob(),
createDefaultDepositUnfreezeJob(),
createDefaultDataRetentionJob(),
createDefaultPddOrderSyncJob(),
],
}
}
function createDefaultPddOrderSyncJob() {
return {
id: PDD_ORDER_SYNC_JOB_ID,
type: 'pdd_order_sync',
enabled: true,
intervalSeconds: 20,
config: {
pageSize: 10,
},
}
}
function createDefaultDataRetentionJob() {
return {
id: DATA_RETENTION_JOB_ID,
@@ -0,0 +1,55 @@
/**
* Pinduoduo Scheduled Order Sync Job
* 拼多多定时订单同步任务 (错峰智能调度与异常保护)
*/
import type { JsonObject } from '../../types/json.js'
import { logInfo } from '../../utils/logger.js'
import { syncAllPddStores } from '../platforms/pdd/pdd-sync-service.js'
export async function runPddOrderSyncJob(job: JsonObject) {
const config =
job.config && typeof job.config === 'object' && !Array.isArray(job.config)
? (job.config as JsonObject)
: {}
const pageSize = Math.min(50, Math.max(1, Number(config.pageSize || 10)))
const syncResult = await syncAllPddStores({ pageSize })
if (!syncResult.enabled) {
return {
ok: true,
status: 'disabled',
message: '拼多多同步子服务已全局停用',
accountCount: syncResult.storeCount,
checkedCount: 0,
okCount: 0,
failedCount: 0,
}
}
const okCount = syncResult.results.filter((r) => !r.error).length
const failedCount = syncResult.results.filter((r) => Boolean(r.error)).length
const totalUpserted = syncResult.results.reduce((acc, r) => acc + r.upsertedCount, 0)
const totalFetched = syncResult.results.reduce((acc, r) => acc + r.fetchedCount, 0)
logInfo('[pdd-order-sync-job]', '拼多多定时拉单执行完成', {
storeCount: syncResult.storeCount,
syncedCount: syncResult.syncedCount,
okCount,
failedCount,
totalFetched,
totalUpserted,
})
return {
ok: failedCount === 0,
status: failedCount > 0 ? (okCount > 0 ? 'partial_error' : 'failed') : 'ok',
message: `已同步 ${syncResult.syncedCount} 个店铺,拉取 ${totalFetched} 单,入库/更新 ${totalUpserted} 单${failedCount > 0 ? `,失败 ${failedCount} 个店铺` : ''}`,
accountCount: syncResult.storeCount,
checkedCount: syncResult.syncedCount,
okCount,
failedCount,
asset: totalUpserted,
}
}
@@ -6,6 +6,7 @@ import { runCloudtentaclesHealthJob } from './cloudtentacles-health-job.js'
import { runDepositUnfreezeJob } from './deposit-unfreeze-job.js'
import { runWorkOrderTimeoutJob } from './work-order-timeout-job.js'
import { runDataRetentionJob } from './data-retention-job.js'
import { runPddOrderSyncJob } from './pdd-order-sync-job.js'
const timers = new Map<string, NodeJS.Timeout>()
const jobStates = new Map<string, JsonObject>()
@@ -79,7 +80,7 @@ function rescheduleJob(job: JsonObject) {
if (!jobId) return
const latestJob = getScheduledJobById(jobId)
if (!latestJob || latestJob.enabled !== true) return
const intervalMs = Math.max(60, Number(latestJob.intervalSeconds || 60)) * 1000
const intervalMs = Math.max(10, Number(latestJob.intervalSeconds || 20)) * 1000
scheduleJob(latestJob, intervalMs)
}
@@ -197,6 +198,9 @@ function dispatchJob(job: JsonObject) {
if (job.type === 'data_retention') {
return runDataRetentionJob()
}
if (job.type === 'pdd_order_sync') {
return runPddOrderSyncJob(job)
}
throw createHttpError(`不支持的定时任务类型:${job.type}`, {
statusCode: 400,