Merge branch 'main' into dev

This commit is contained in:
yml2213
2026-09-15 14:17:36 +08:00
216 changed files with 5490 additions and 23718 deletions
@@ -1,189 +0,0 @@
import crypto from 'node:crypto'
import fs from 'node:fs/promises'
import path from 'node:path'
import { Router } from 'express'
import { PROJECT_ROOT } from '../../config/runtime.js'
import {
listAdminCloudtentaclesDeliveryRecords,
listAdminCloudtentaclesRecordSources,
} from '../../services/admin/platform-config/cloudtentacles/index.js'
import { createJsonHandler, requireAdminRoles } from './session.js'
import { createHttpError, sendRouteError } from '../../utils/http.js'
import type { AdminCloudtentaclesDeliveryRecordQueryRouteBody } from '../../types/admin/route-inputs.js'
const router = Router()
const RECORD_IMAGE_CACHE_DIR = path.join(
PROJECT_ROOT,
'data',
'cache',
'cloudtentacles-record-images',
)
router.get(
'/cloudtentacles-records/sources',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(() => listAdminCloudtentaclesRecordSources(), {
successMessage: 'ok',
errorMessage: '读取 cloudtentacles 账号列表失败',
scope: '[admin/cloudtentacles-records/sources]',
}),
)
router.post(
'/cloudtentacles-records',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(
(req) =>
listAdminCloudtentaclesDeliveryRecords(
req.body as AdminCloudtentaclesDeliveryRecordQueryRouteBody,
),
{
successMessage: 'cloudtentacles 发货记录查询成功',
errorMessage: 'cloudtentacles 发货记录查询失败',
scope: '[admin/cloudtentacles-records]',
},
),
)
router.get(
'/cloudtentacles-records/image',
requireAdminRoles(['admin', 'operator', 'support']),
async (req, res) => {
try {
const imageUrl = normalizeAllowedImageUrl(req.query.url)
const cachedImage = await getCachedRecordImage(imageUrl)
res.setHeader('Content-Type', cachedImage.contentType)
res.setHeader('Cache-Control', 'public, max-age=86400')
res.send(cachedImage.buffer)
} catch (error) {
sendRouteError(
res,
error,
'读取 cloudtentacles 商品图片失败',
'[admin/cloudtentacles-records/image]',
)
}
},
)
export default router
function normalizeAllowedImageUrl(value: unknown) {
const raw = String(value || '').trim()
if (!raw) {
throw createHttpError('缺少商品图片地址', {
statusCode: 400,
errorCode: 'cloudtentacles_record_image_url_required',
})
}
const url = new URL(raw)
if (!['https:', 'http:'].includes(url.protocol) || url.hostname !== 'gp.playinjoy.com') {
throw createHttpError('商品图片地址不在允许范围内', {
statusCode: 400,
errorCode: 'cloudtentacles_record_image_url_not_allowed',
})
}
return url.toString()
}
async function getCachedRecordImage(imageUrl: string) {
const cacheMeta = buildRecordImageCacheMeta(imageUrl)
const cached = await readCachedRecordImage(cacheMeta)
if (cached) {
return cached
}
const downloaded = await downloadRecordImage(imageUrl)
const filePath = cacheMeta.filePathForContentType(downloaded.contentType)
await fs.mkdir(RECORD_IMAGE_CACHE_DIR, { recursive: true })
await fs.writeFile(filePath, downloaded.buffer)
return downloaded
}
async function readCachedRecordImage(cacheMeta: ReturnType<typeof buildRecordImageCacheMeta>) {
for (const candidate of cacheMeta.candidateFilePaths) {
try {
const buffer = await fs.readFile(candidate.filePath)
return {
buffer,
contentType: candidate.contentType,
}
} catch (error) {
const code = String((error as NodeJS.ErrnoException).code || '')
if (code !== 'ENOENT') {
throw error
}
}
}
return null
}
async function downloadRecordImage(imageUrl: string) {
const response = await fetch(imageUrl, {
headers: {
accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
referer: 'https://gp.playinjoy.com/',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36',
},
})
if (!response.ok) {
throw createHttpError(`商品图片读取失败,HTTP ${response.status}`, {
statusCode: 502,
errorCode: 'cloudtentacles_record_image_fetch_failed',
})
}
const contentType = response.headers.get('content-type') || 'image/jpeg'
if (!contentType.startsWith('image/')) {
throw createHttpError('商品图片响应格式无效', {
statusCode: 502,
errorCode: 'cloudtentacles_record_image_invalid_content_type',
})
}
return {
buffer: Buffer.from(await response.arrayBuffer()),
contentType: normalizeImageContentType(contentType),
}
}
function buildRecordImageCacheMeta(imageUrl: string) {
const digest = crypto.createHash('sha256').update(imageUrl).digest('hex')
const candidateTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif']
return {
candidateFilePaths: candidateTypes.map((contentType) => ({
contentType,
filePath: path.join(
RECORD_IMAGE_CACHE_DIR,
`${digest}.${extensionForContentType(contentType)}`,
),
})),
filePathForContentType(contentType: string) {
return path.join(RECORD_IMAGE_CACHE_DIR, `${digest}.${extensionForContentType(contentType)}`)
},
}
}
function normalizeImageContentType(contentType: string) {
return (
String(contentType || 'image/jpeg')
.split(';')[0]
?.trim()
.toLowerCase() || 'image/jpeg'
)
}
function extensionForContentType(contentType: string) {
const normalized = normalizeImageContentType(contentType)
if (normalized === 'image/png') return 'png'
if (normalized === 'image/webp') return 'webp'
if (normalized === 'image/gif') return 'gif'
return 'jpg'
}
-40
View File
@@ -3,9 +3,7 @@ import { Router } from 'express'
import {
buildOpen91QueryMock,
createAffiliateDashMockClaim,
createFeifeiMockClaim,
createKuaishouIndustryVoucherMockData,
createLewanMockClaim,
getDevMockStatus,
isDevMockEnabled,
} from '../../services/dev-mock/dev-mock-service.js'
@@ -41,44 +39,6 @@ router.get(
router.use('/dev-mock', requireAdminRoles(['admin', 'operator']), requireDevMockEnabled())
router.post(
'/dev-mock/lewan',
createJsonHandler((req) => createLewanMockClaim(req.body || {}), {
successMessage: 'lewan mock 已生成',
errorMessage: '生成 lewan mock 失败',
scope: '[admin/dev-mock/lewan]',
audit: (_req, data) => ({
action: 'dev_mock_create_lewan',
targetType: 'dev_mock',
targetId: String((data as { orderNo?: string })?.orderNo || ''),
data: {
platform: 'lewan',
orderNo: (data as { orderNo?: string })?.orderNo,
step: (data as { step?: string })?.step,
},
}),
}),
)
router.post(
'/dev-mock/feifei',
createJsonHandler((req) => createFeifeiMockClaim(req.body || {}), {
successMessage: 'feifei mock 已生成',
errorMessage: '生成 feifei mock 失败',
scope: '[admin/dev-mock/feifei]',
audit: (_req, data) => ({
action: 'dev_mock_create_feifei',
targetType: 'dev_mock',
targetId: String((data as { orderNo?: string })?.orderNo || ''),
data: {
platform: 'feifei',
orderNo: (data as { orderNo?: string })?.orderNo,
step: (data as { step?: string })?.step,
},
}),
}),
)
router.post(
'/dev-mock/affiliate-dash',
createJsonHandler((req) => createAffiliateDashMockClaim(req.body || {}), {
+1 -1
View File
@@ -1,7 +1,7 @@
import { Router } from 'express'
import { getAdminOrderDetail, getAdminOrders } from '../../services/admin/admin-read-service.js'
import { resendAdminOrderKuaishouIndustryVoucherCodes } from '../../services/admin/write/kuaishou-cloud-actions.js'
import { resendAdminOrderKuaishouIndustryVoucherCodes } from '../../services/admin/write/kuaishou-industry-actions.js'
import { createJsonHandler, requireAdminRoles } from './session.js'
import type { AdminOrderRouteParams, AdminOrderRouteQuery } from '../../types/admin/route-inputs.js'
@@ -1,9 +1,7 @@
import { Router } from 'express'
import { requireAdminRoles } from './session.js'
import cloudtentaclesRouter from './platform-config/cloudtentacles.js'
import kuaishouIndustryRouter from './platform-config/kuaishou-industry.js'
import kuaishouFeifeiRouter from './platform-config/kuaishou-feifei.js'
import ninetyoneRouter from './platform-config/ninetyone.js'
import notificationsRouter from './platform-config/notifications.js'
import fulfillmentRoutingRouter from './platform-config/fulfillment-routing.js'
@@ -15,10 +13,8 @@ const router = Router()
router.use('/platform-config', kuaishouIndustryRouter)
router.use('/platform-config', requireAdminRoles(['admin']))
router.use('/platform-config', notificationsRouter)
router.use('/platform-config', kuaishouFeifeiRouter)
router.use('/platform-config', ninetyoneRouter)
router.use('/platform-config', fulfillmentRoutingRouter)
router.use('/platform-config', cloudtentaclesRouter)
router.use('/platform-config', affiliateDashRouter)
router.use('/platform-config', pddRouter)
@@ -1,387 +0,0 @@
import { Router } from 'express'
import {
appointAdminCloudtentaclesVirtualNumber,
backAdminCloudtentaclesVirtualNumber,
buyAdminCloudtentaclesSku,
deleteAdminCloudtentaclesSource,
fetchAdminCloudtentaclesVirtualNumberCode,
generateAdminCloudtentaclesLoginCode,
getAdminCloudtentaclesOverrideRules,
getAdminCloudtentaclesAsset,
getAdminCloudtentaclesBindUrl,
getAdminCloudtentaclesCategories,
getAdminCloudtentaclesKnapsack,
getAdminCloudtentaclesSkuList,
listAdminCloudtentaclesSources,
listAdminCloudtentaclesVirtualNumbers,
runAdminCloudtentaclesFullFlow,
sendAdminCloudtentaclesSmsCode,
testAdminCloudtentaclesLogin,
updateAdminCloudtentaclesOverrideRules,
updateAdminCloudtentaclesSourceConfig,
useAdminCloudtentaclesSku,
validateAdminCloudtentaclesSession,
verifyAdminCloudtentaclesLoginCode,
} from '../../../services/admin/platform-config/cloudtentacles/index.js'
import type {
AdminCloudtentaclesCatalogQueryRouteBody,
AdminCloudtentaclesFullFlowRouteBody,
AdminCloudtentaclesOverrideRuleConfigRouteBody,
AdminCloudtentaclesSendSmsCodeRouteBody,
AdminCloudtentaclesSkuBuyRouteBody,
AdminCloudtentaclesSkuUseRouteBody,
AdminCloudtentaclesSourceConfigRouteBody,
AdminCloudtentaclesTestLoginRouteBody,
AdminCloudtentaclesValidateSessionRouteBody,
AdminCloudtentaclesVirtualNumberRouteBody,
} from '../../../types/admin/route-inputs.js'
import { createJsonHandler } from '../session.js'
import type { JsonRecord } from '../../../types/json.js'
const router = Router()
router.get(
'/cloudtentacles-source',
createJsonHandler(() => listAdminCloudtentaclesSources(), {
successMessage: 'ok',
errorMessage: '读取 cloudtentacles 履约平台配置失败',
scope: '[admin/platform-config/cloudtentacles-source]',
}),
)
router.delete(
'/cloudtentacles-source/:sourceKey',
createJsonHandler(
(req) => deleteAdminCloudtentaclesSource(String(req.params.sourceKey || '').trim()),
{
successMessage: 'cloudtentacles 履约平台配置已删除',
errorMessage: '删除 cloudtentacles 履约平台配置失败',
scope: '[admin/platform-config/cloudtentacles-source/:sourceKey]',
audit: (req) => ({
action: 'platform_cloudtentacles_source_deleted',
targetType: 'platform_config',
targetId: String(req.params.sourceKey || '').trim(),
data: {},
}),
},
),
)
router.post(
'/cloudtentacles-source',
createJsonHandler(
(req) =>
updateAdminCloudtentaclesSourceConfig(req.body as AdminCloudtentaclesSourceConfigRouteBody),
{
successMessage: 'cloudtentacles 履约平台配置已保存',
errorMessage: '保存 cloudtentacles 履约平台配置失败',
scope: '[admin/platform-config/cloudtentacles-source]',
audit: (_req, data) => {
const result = data as JsonRecord
return {
action: 'platform_cloudtentacles_source_updated',
targetType: 'platform_config',
targetId: 'cloudtentacles_source',
data: {
filePath: String(result.filePath || '').trim(),
username: String(result.source?.username || '').trim(),
enabled: Boolean(result.source?.enabled),
},
}
},
},
),
)
router.get(
'/cloudtentacles/override-rules',
createJsonHandler(() => getAdminCloudtentaclesOverrideRules(), {
successMessage: 'ok',
errorMessage: '读取 cloudtentacles 覆盖规则失败',
scope: '[admin/platform-config/cloudtentacles/override-rules]',
}),
)
router.post(
'/cloudtentacles/override-rules',
createJsonHandler(
(req) =>
updateAdminCloudtentaclesOverrideRules(
req.body as AdminCloudtentaclesOverrideRuleConfigRouteBody,
),
{
successMessage: 'cloudtentacles 覆盖规则已保存',
errorMessage: '保存 cloudtentacles 覆盖规则失败',
scope: '[admin/platform-config/cloudtentacles/override-rules]',
audit: (_req, data) => {
const result = data as JsonRecord
return {
action: 'platform_cloudtentacles_override_rules_updated',
targetType: 'platform_config',
targetId: 'cloudtentacles_override_rules',
data: {
filePath: String(result.filePath || '').trim(),
enabled: result.enabled !== false,
ruleCount: Array.isArray(result.rules) ? result.rules.length : 0,
},
}
},
},
),
)
router.post(
'/cloudtentacles/send-sms-code',
createJsonHandler(
(req) => sendAdminCloudtentaclesSmsCode(req.body as AdminCloudtentaclesSendSmsCodeRouteBody),
{
successMessage: 'cloudtentacles 短信验证码已发送',
errorMessage: 'cloudtentacles 发送短信验证码失败',
scope: '[admin/platform-config/cloudtentacles/send-sms-code]',
audit: (req, data) => {
const body = req.body as AdminCloudtentaclesSendSmsCodeRouteBody
const result = data as JsonRecord
return {
action: 'platform_cloudtentacles_send_sms_code',
targetType: 'platform_config',
targetId: String(result.username || body.username || '').trim() || 'cloudtentacles',
data: {
baseUrl: result.baseUrl || String(body.baseUrl || '').trim(),
phoneMasked: result.phoneMasked || '',
},
}
},
},
),
)
router.post(
'/cloudtentacles/test-login',
createJsonHandler(
(req) => testAdminCloudtentaclesLogin(req.body as AdminCloudtentaclesTestLoginRouteBody),
{
successMessage: 'cloudtentacles 登录测试成功',
errorMessage: 'cloudtentacles 登录测试失败',
scope: '[admin/platform-config/cloudtentacles/test-login]',
audit: (req, data) => {
const body = req.body as AdminCloudtentaclesTestLoginRouteBody
const result = data as JsonRecord
return {
action: 'platform_cloudtentacles_test_login',
targetType: 'platform_config',
targetId: String(result.username || body.username || '').trim() || 'cloudtentacles',
data: {
baseUrl: result.baseUrl || String(body.baseUrl || '').trim(),
permissionCount: Number(result.session?.permissionCount || 0),
},
}
},
},
),
)
router.post(
'/cloudtentacles/validate-session',
createJsonHandler(
(req) =>
validateAdminCloudtentaclesSession(req.body as AdminCloudtentaclesValidateSessionRouteBody),
{
successMessage: 'cloudtentacles 会话校验成功',
errorMessage: 'cloudtentacles 会话校验失败',
scope: '[admin/platform-config/cloudtentacles/validate-session]',
audit: (_req, data) => {
const result = data as JsonRecord
return {
action: 'platform_cloudtentacles_validate_session',
targetType: 'platform_config',
targetId: 'cloudtentacles_session',
data: {
baseUrl: String(result.baseUrl || '').trim(),
permissionCount: Number(result.session?.permissionCount || 0),
},
}
},
},
),
)
router.post(
'/cloudtentacles/asset',
createJsonHandler(
(req) => getAdminCloudtentaclesAsset(req.body as AdminCloudtentaclesCatalogQueryRouteBody),
{
successMessage: 'cloudtentacles 余额查询成功',
errorMessage: 'cloudtentacles 余额查询失败',
scope: '[admin/platform-config/cloudtentacles/asset]',
},
),
)
router.post(
'/cloudtentacles/categories',
createJsonHandler(
(req) => getAdminCloudtentaclesCategories(req.body as AdminCloudtentaclesCatalogQueryRouteBody),
{
successMessage: 'cloudtentacles 分类查询成功',
errorMessage: 'cloudtentacles 分类查询失败',
scope: '[admin/platform-config/cloudtentacles/categories]',
},
),
)
router.post(
'/cloudtentacles/sku/list',
createJsonHandler(
(req) => getAdminCloudtentaclesSkuList(req.body as AdminCloudtentaclesCatalogQueryRouteBody),
{
successMessage: 'cloudtentacles SKU 列表查询成功',
errorMessage: 'cloudtentacles SKU 列表查询失败',
scope: '[admin/platform-config/cloudtentacles/sku/list]',
},
),
)
router.post(
'/cloudtentacles/sku/buy',
createJsonHandler(
(req) => buyAdminCloudtentaclesSku(req.body as AdminCloudtentaclesSkuBuyRouteBody),
{
successMessage: 'cloudtentacles SKU 购买成功',
errorMessage: 'cloudtentacles SKU 购买失败',
scope: '[admin/platform-config/cloudtentacles/sku/buy]',
},
),
)
router.post(
'/cloudtentacles/sku/use',
createJsonHandler(
(req) => useAdminCloudtentaclesSku(req.body as AdminCloudtentaclesSkuUseRouteBody),
{
successMessage: 'cloudtentacles 发货成功',
errorMessage: 'cloudtentacles 发货失败',
scope: '[admin/platform-config/cloudtentacles/sku/use]',
},
),
)
router.post(
'/cloudtentacles/knapsack',
createJsonHandler(
(req) => getAdminCloudtentaclesKnapsack(req.body as AdminCloudtentaclesCatalogQueryRouteBody),
{
successMessage: 'cloudtentacles 背包查询成功',
errorMessage: 'cloudtentacles 背包查询失败',
scope: '[admin/platform-config/cloudtentacles/knapsack]',
},
),
)
router.post(
'/cloudtentacles/vn/list',
createJsonHandler(
(req) =>
listAdminCloudtentaclesVirtualNumbers(req.body as AdminCloudtentaclesVirtualNumberRouteBody),
{
successMessage: 'cloudtentacles 虚拟号列表查询成功',
errorMessage: 'cloudtentacles 虚拟号列表查询失败',
scope: '[admin/platform-config/cloudtentacles/vn/list]',
},
),
)
router.post(
'/cloudtentacles/vn/appoint',
createJsonHandler(
(req) =>
appointAdminCloudtentaclesVirtualNumber(
req.body as AdminCloudtentaclesVirtualNumberRouteBody,
),
{
successMessage: 'cloudtentacles 虚拟号申请成功',
errorMessage: 'cloudtentacles 虚拟号申请失败',
scope: '[admin/platform-config/cloudtentacles/vn/appoint]',
},
),
)
router.post(
'/cloudtentacles/vn/generate-login-code',
createJsonHandler(
(req) =>
generateAdminCloudtentaclesLoginCode(req.body as AdminCloudtentaclesVirtualNumberRouteBody),
{
successMessage: 'cloudtentacles 登录码生成成功',
errorMessage: 'cloudtentacles 登录码生成失败',
scope: '[admin/platform-config/cloudtentacles/vn/generate-login-code]',
},
),
)
router.post(
'/cloudtentacles/vn/fetch-code',
createJsonHandler(
(req) =>
fetchAdminCloudtentaclesVirtualNumberCode(
req.body as AdminCloudtentaclesVirtualNumberRouteBody,
),
{
successMessage: 'cloudtentacles 验证码获取成功',
errorMessage: 'cloudtentacles 验证码获取失败',
scope: '[admin/platform-config/cloudtentacles/vn/fetch-code]',
},
),
)
router.post(
'/cloudtentacles/vn/verify-code',
createJsonHandler(
(req) =>
verifyAdminCloudtentaclesLoginCode(req.body as AdminCloudtentaclesVirtualNumberRouteBody),
{
successMessage: 'cloudtentacles 登录码校验成功',
errorMessage: 'cloudtentacles 登录码校验失败',
scope: '[admin/platform-config/cloudtentacles/vn/verify-code]',
},
),
)
router.post(
'/cloudtentacles/vn/bind-url',
createJsonHandler(
(req) => getAdminCloudtentaclesBindUrl(req.body as AdminCloudtentaclesVirtualNumberRouteBody),
{
successMessage: 'cloudtentacles 兑换链接获取成功',
errorMessage: 'cloudtentacles 兑换链接获取失败',
scope: '[admin/platform-config/cloudtentacles/vn/bind-url]',
},
),
)
router.post(
'/cloudtentacles/vn/back',
createJsonHandler(
(req) =>
backAdminCloudtentaclesVirtualNumber(req.body as AdminCloudtentaclesVirtualNumberRouteBody),
{
successMessage: 'cloudtentacles 号码退还成功',
errorMessage: 'cloudtentacles 号码退还失败',
scope: '[admin/platform-config/cloudtentacles/vn/back]',
},
),
)
router.post(
'/cloudtentacles/debug/full-flow',
createJsonHandler(
(req) => runAdminCloudtentaclesFullFlow(req.body as AdminCloudtentaclesFullFlowRouteBody),
{
successMessage: 'cloudtentacles 完整调试流程执行成功',
errorMessage: 'cloudtentacles 完整调试流程执行失败',
scope: '[admin/platform-config/cloudtentacles/debug/full-flow]',
},
),
)
export default router
@@ -1,124 +0,0 @@
import { Router } from 'express'
import {
createAdminKuaishouFeifeiTestOrder,
getAdminKuaishouFeifeiConfig,
listAdminKuaishouFeifeiProducts,
matchAdminKuaishouFeifeiProduct,
queryAdminKuaishouFeifeiTestOrder,
syncAdminKuaishouFeifeiProductRules,
updateAdminKuaishouFeifeiConfig,
} from '../../../services/admin/platform-config/kuaishou-feifei-service.js'
import { createJsonHandler } from '../session.js'
import type { JsonRecord } from '../../../types/json.js'
const router = Router()
router.get(
'/kuaishou-feifei',
createJsonHandler(() => getAdminKuaishouFeifeiConfig(), {
successMessage: 'ok',
errorMessage: '读取 kuaishou-feifei 配置失败',
scope: '[admin/platform-config/kuaishou-feifei]',
}),
)
router.post(
'/kuaishou-feifei',
createJsonHandler((req) => updateAdminKuaishouFeifeiConfig(req.body as JsonRecord), {
successMessage: 'kuaishou-feifei 配置已保存',
errorMessage: '保存 kuaishou-feifei 配置失败',
scope: '[admin/platform-config/kuaishou-feifei]',
audit: (_req, data) => {
const result = data as JsonRecord
const source = result.source as JsonRecord | undefined
return {
action: 'platform_kuaishou_feifei_config_updated',
targetType: 'platform_config',
targetId: 'kuaishou_feifei',
data: {
filePath: String(result.filePath || '').trim(),
enabled: source?.enabled !== false,
ruleCount: Array.isArray(source?.productRules) ? source.productRules.length : 0,
},
}
},
}),
)
router.post(
'/kuaishou-feifei/match',
createJsonHandler((req) => matchAdminKuaishouFeifeiProduct(req.body as JsonRecord), {
successMessage: 'ok',
errorMessage: '匹配 kuaishou-feifei 商品失败',
scope: '[admin/platform-config/kuaishou-feifei/match]',
}),
)
router.post(
'/kuaishou-feifei/products',
createJsonHandler((req) => listAdminKuaishouFeifeiProducts(req.body as JsonRecord), {
successMessage: 'ok',
errorMessage: '查询 kuaishou-feifei 商品失败',
scope: '[admin/platform-config/kuaishou-feifei/products]',
}),
)
router.post(
'/kuaishou-feifei/sync-products',
createJsonHandler((req) => syncAdminKuaishouFeifeiProductRules(req.body as JsonRecord), {
successMessage: 'kuaishou-feifei 商品映射已同步',
errorMessage: '同步 kuaishou-feifei 商品映射失败',
scope: '[admin/platform-config/kuaishou-feifei/sync-products]',
audit: (_req, data) => {
const result = data as JsonRecord
const sync = result.sync as JsonRecord | undefined
return {
action: 'platform_kuaishou_feifei_products_synced',
targetType: 'platform_config',
targetId: 'kuaishou_feifei',
data: {
productCount: Number(sync?.productCount || 0) || 0,
ruleCount: Number(sync?.ruleCount || 0) || 0,
status: String(sync?.status || '').trim(),
},
}
},
}),
)
router.post(
'/kuaishou-feifei/test-order',
createJsonHandler((req) => createAdminKuaishouFeifeiTestOrder(req.body as JsonRecord), {
successMessage: 'kuaishou-feifei 测试单已创建',
errorMessage: '创建 kuaishou-feifei 测试单失败',
scope: '[admin/platform-config/kuaishou-feifei/test-order]',
audit: (_req, data) => {
const result = data as JsonRecord
return {
action: 'platform_kuaishou_feifei_test_order_created',
targetType: 'platform_config',
targetId: 'kuaishou_feifei',
data: {
orderNo: String(result.orderNo || '').trim(),
platformOrderNo: String(result.platformOrderNo || '').trim(),
productCode: String(result.productCode || '').trim(),
rechargeStatus: Number(result.rechargeStatus || 0) || 0,
},
}
},
}),
)
router.post(
'/kuaishou-feifei/query-order',
createJsonHandler((req) => queryAdminKuaishouFeifeiTestOrder(req.body as JsonRecord), {
successMessage: 'ok',
errorMessage: '查询 kuaishou-feifei 订单失败',
scope: '[admin/platform-config/kuaishou-feifei/query-order]',
}),
)
export default router
@@ -13,6 +13,7 @@ import {
deletePddStore,
getPddStoreById,
updatePddStoreCheckStatus,
generatePddStoreSyncToken,
} from '../../../services/platforms/pdd/pdd-source-config-service.js'
import { notifyPddCookieExpired } from '../../../services/notification/domain-notifications.js'
import {
@@ -102,6 +103,35 @@ router.delete(
}),
)
/**
* POST /platform-config/pdd/stores/:shopId/sync-token
* 为指定拼多多店铺生成或重置浏览器插件配对密钥。
*/
router.post(
'/pdd/stores/:shopId/sync-token',
createJsonHandler(
async (req) => {
const syncToken = await generatePddStoreSyncToken(req.params.shopId)
const store = getPddStoreById(req.params.shopId)
return {
shopId: req.params.shopId,
shopName: store?.shopName || req.params.shopId,
syncToken,
}
},
{
successMessage: '店铺插件配对密钥已生成',
errorMessage: '生成插件配对密钥失败',
scope: '[admin/platform-config/pdd/stores/:shopId/sync-token]',
audit: (req) => ({
action: 'platform_pdd_store_token_generated',
targetType: 'platform_store',
targetId: String(req.params.shopId || ''),
}),
},
),
)
/**
* POST /platform-config/pdd/test-cookie
* Test Cookie validity for a given cookie string or an existing shop.
+63 -75
View File
@@ -2,23 +2,28 @@ import type { Request } from 'express'
import { Router } from 'express'
import { getAdminTaskDetail, getAdminTasks } from '../../services/admin/admin-read-service.js'
import { closeAdminTask } from '../../services/admin/write/task-actions.js'
import {
closeAdminTask,
completeAdminTaskManualDispatch,
markAdminTaskManualReview,
regenerateAdminTaskClaimLink,
retryAdminTask,
} from '../../services/admin/write/task-actions.js'
import {
consumeAdminTaskKuaishouIndustryVoucher,
dispatchAdminTaskKuaishouCloudFulfillment,
prepareAdminTaskKuaishouCloudFulfillment,
rebindAdminTaskKuaishouCloudRole,
refreshAdminTaskKuaishouCloudRoleInfo,
resendAdminTaskKuaishouIndustryVoucherCode,
returnNumberAdminTaskKuaishouCloudFulfillment,
} from '../../services/admin/write/kuaishou-cloud-actions.js'
} from '../../services/admin/write/kuaishou-industry-actions.js'
import { createJsonHandler, requireAdminRoles } from './session.js'
import type {
AdminTaskKuaishouIndustryConsumeRouteBody,
AdminTaskManualDispatchRouteBody,
AdminTaskRouteParams,
AdminTaskRouteQuery,
} from '../../types/admin/route-inputs.js'
import type { AdminTaskActionResponse } from '../../types/admin/write-models.js'
import type {
AdminTaskActionResponse,
AdminTaskManualDispatchResponse,
} from '../../types/admin/write-models.js'
const router = Router()
@@ -49,6 +54,31 @@ router.get(
}),
)
router.post(
'/tasks/:taskId/retry',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => retryAdminTask(getTaskId(req), req.adminSession || null), {
successMessage: '任务已重试',
errorMessage: '重试任务失败',
scope: '[admin/tasks/:taskId/retry]',
audit: (req, data) => buildTaskAudit('task_retry', req, data),
}),
)
router.post(
'/tasks/:taskId/regenerate-claim-link',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(
(req) => regenerateAdminTaskClaimLink(getTaskId(req), req.adminSession || null),
{
successMessage: '领取链接已重新生成',
errorMessage: '重新生成领取链接失败',
scope: '[admin/tasks/:taskId/regenerate-claim-link]',
audit: (req, data) => buildTaskAudit('task_regenerate_claim_link', req, data),
},
),
)
router.post(
'/tasks/:taskId/close',
requireAdminRoles(['admin', 'operator', 'support']),
@@ -61,69 +91,42 @@ router.post(
)
router.post(
'/tasks/:taskId/kuaishou-cloud/prepare',
'/tasks/:taskId/mark-manual-review',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => markAdminTaskManualReview(getTaskId(req), req.adminSession || null), {
successMessage: '任务已转人工处理',
errorMessage: '标记人工处理失败',
scope: '[admin/tasks/:taskId/mark-manual-review]',
audit: (req, data) => buildTaskAudit('task_mark_manual_review', req, data),
}),
)
router.post(
'/tasks/:taskId/complete-manual-dispatch',
requireAdminRoles(['admin', 'operator']),
createJsonHandler(
(req) => prepareAdminTaskKuaishouCloudFulfillment(getTaskId(req), req.adminSession || null),
(req) =>
completeAdminTaskManualDispatch(
getTaskId(req),
req.body as AdminTaskManualDispatchRouteBody,
req.adminSession || null,
),
{
successMessage: '绑定资源已准备完成',
errorMessage: '准备绑定资源失败',
scope: '[admin/tasks/:taskId/kuaishou-cloud/prepare]',
audit: (req, data) => buildTaskAudit('task_kuaishou_cloud_prepare', req, data),
},
),
)
router.post(
'/tasks/:taskId/kuaishou-cloud/refresh-role-info',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(
(req) => refreshAdminTaskKuaishouCloudRoleInfo(getTaskId(req), req.adminSession || null),
{
successMessage: '角色信息已刷新',
errorMessage: '刷新角色信息失败',
scope: '[admin/tasks/:taskId/kuaishou-cloud/refresh-role-info]',
audit: (req, data) => buildTaskAudit('task_kuaishou_cloud_role_info_refreshed', req, data),
},
),
)
router.post(
'/tasks/:taskId/kuaishou-cloud/rebind-role',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(
(req) => rebindAdminTaskKuaishouCloudRole(getTaskId(req), req.adminSession || null),
{
// 旧号已回收时会自动跳过退号并新取号出链
successMessage: '换绑完成:已生成新的绑定链接(原领取链接可继续使用)',
errorMessage: '换绑角色失败',
scope: '[admin/tasks/:taskId/kuaishou-cloud/rebind-role]',
audit: (req, data) => buildTaskAudit('task_kuaishou_cloud_rebind_role', req, data),
},
),
)
router.post(
'/tasks/:taskId/kuaishou-cloud/dispatch',
requireAdminRoles(['admin', 'operator']),
createJsonHandler(
(req) => dispatchAdminTaskKuaishouCloudFulfillment(getTaskId(req), req.adminSession || null),
{
successMessage: '已完成绑定确认并发货',
errorMessage: '执行发货失败',
scope: '[admin/tasks/:taskId/kuaishou-cloud/dispatch]',
successMessage: '人工履约结果已回写',
errorMessage: '回写人工履约结果失败',
scope: '[admin/tasks/:taskId/complete-manual-dispatch]',
audit: (req, data) => {
const result = data as AdminTaskActionResponse
const result = data as AdminTaskManualDispatchResponse
return {
action: 'task_kuaishou_cloud_dispatch',
action: 'task_complete_manual_dispatch',
targetType: 'task',
targetId: String(getTaskId(req)),
data: {
taskId: result.task.taskId,
taskNo: result.task.taskNo,
status: result.task.status,
outcome: result.outcome,
deliveryStatus: result.task.deliveryStatus,
resultCode: result.task.resultCode,
},
}
},
@@ -131,21 +134,6 @@ router.post(
),
)
router.post(
'/tasks/:taskId/kuaishou-cloud/return-number',
requireAdminRoles(['admin', 'operator']),
createJsonHandler(
(req) =>
returnNumberAdminTaskKuaishouCloudFulfillment(getTaskId(req), req.adminSession || null),
{
successMessage: '号码已退还',
errorMessage: '退还号码失败',
scope: '[admin/tasks/:taskId/kuaishou-cloud/return-number]',
audit: (req, data) => buildTaskAudit('task_kuaishou_cloud_return_number', req, data),
},
),
)
router.post(
'/tasks/:taskId/kuaishou-industry/resend-code',
requireAdminRoles(['admin', 'operator', 'support']),