feat(douyin-mashida): 彻底重构优化配置面板,支持一键填域名/订阅推送/网关自检并剔除冗余字段

- 剔除历史旧卡券时代废弃的 afterVerifyLink(去使用链接)与 afterVerifyMessage(通知正文)
- 面板新增一键填入当前域名、向开放平台一键订阅推送、停用推送以及网关连通与签名自检功能
- 业务配置重构为「基础接入与商户凭据」、「买家 H5 直充与发货私信」、「高级网关设置」三层清晰结构
- 后端与前端服务新增 /douyin-mashida/subscribe 与 /douyin-mashida/test-gateway 统一接口

验证方式:前端类型检查与打包构建通过,全量 132 项前端单测与 674 项后端单测全部通过
This commit is contained in:
yml2213
2026-10-10 20:05:46 +08:00
parent 748c18bc7d
commit d8ce40e346
5 changed files with 611 additions and 237 deletions
@@ -3,6 +3,8 @@ import { Router } from 'express'
import {
getAdminDouyinMashidaConfig,
saveAdminDouyinMashidaConfig,
subscribeAdminDouyinMashidaPush,
testAdminDouyinMashidaGateway,
} from '../../../services/admin/platform-config/douyin-mashida-service.js'
import { createJsonHandler } from '../session.js'
import type { JsonRecord } from '../../../types/json.js'
@@ -49,4 +51,35 @@ router.post(
}),
)
router.post(
'/douyin-mashida/subscribe',
createJsonHandler((req) => subscribeAdminDouyinMashidaPush(req.body as JsonRecord), {
successMessage: '平台推送设置成功',
errorMessage: '平台推送设置失败',
scope: '[admin/platform-config/douyin-mashida/subscribe]',
audit: (req, _data) => {
const payload = req.body as JsonRecord
return {
action: 'platform_douyin_mashida_subscribe_updated',
targetType: 'platform_config',
targetId: 'douyin_mashida',
data: {
disable: Boolean(payload?.disable),
messageUrl: String(payload?.messageUrl || payload?.publicUrl || ''),
},
}
},
}),
)
router.post(
'/douyin-mashida/test-gateway',
createJsonHandler(() => testAdminDouyinMashidaGateway(), {
successMessage: '网关测试完成',
errorMessage: '网关测试失败',
scope: '[admin/platform-config/douyin-mashida/test-gateway]',
}),
)
export default router
@@ -1,6 +1,11 @@
import type { JsonObject } from '../../../types/json.js'
import { createHttpError } from '../../../utils/http.js'
import { maskSecret } from '../../../utils/masking.js'
import {
configureDouyinMashidaMessage,
fetchDouyinMashidaOrderDetail,
} from '../../platforms/douyin-mashida/gateway-client.js'
import {
getDouyinMashidaSourceConfig,
saveDouyinMashidaSourceConfig,
@@ -32,6 +37,103 @@ export async function saveAdminDouyinMashidaConfig(payload: JsonObject = {}) {
return mapAdminDouyinMashidaConfig(saved)
}
/** 向开放平台订阅或停用消息推送(message.config) */
export async function subscribeAdminDouyinMashidaPush(
payload: {
publicUrl?: string
messageUrl?: string
topics?: string[] | string
disable?: boolean
} = {},
) {
const config = getDouyinMashidaSourceConfig()
if (!config.enabled) {
throw createHttpError('抖音码速达接入未启用,请先开启接入开关', {
statusCode: 409,
errorCode: 'douyin_mashida_disabled',
})
}
if (!config.uid || !config.secret) {
throw createHttpError('抖音码速达缺少 uid 或用户秘钥,请先在面板中配置并保存', {
statusCode: 409,
errorCode: 'douyin_mashida_credentials_missing',
})
}
const disable = Boolean(payload.disable)
let messageUrl = String(payload.messageUrl || '').trim()
if (!messageUrl && payload.publicUrl) {
messageUrl = `${String(payload.publicUrl).trim().replace(/\/+$/, '')}/api/v1/open/douyin-mashida/message`
}
if (!disable && !messageUrl) {
throw createHttpError('缺少推送地址 (messageUrl),请提供公网地址', {
statusCode: 400,
errorCode: 'missing_message_url',
})
}
const topics = Array.isArray(payload.topics)
? payload.topics.join(',')
: String(payload.topics || config.handledTopics.join(',')).trim()
const response = await configureDouyinMashidaMessage({
status: disable ? 0 : 1,
messageUrl: disable ? undefined : messageUrl,
topics: disable ? undefined : topics,
config,
})
return {
ok: true,
status: disable ? 0 : 1,
messageUrl: disable ? '' : messageUrl,
topics: disable ? '' : topics,
gatewayResponse: response,
}
}
/** 测试网关连通性与签名(使用真实凭据打 order.orderDetail 探测) */
export async function testAdminDouyinMashidaGateway() {
const config = getDouyinMashidaSourceConfig()
if (!config.enabled) {
throw createHttpError('抖音码速达接入未启用,请先开启接入开关', {
statusCode: 409,
errorCode: 'douyin_mashida_disabled',
})
}
if (!config.uid || !config.secret) {
throw createHttpError('抖音码速达缺少 uid 或用户秘钥,请先在面板中配置并保存', {
statusCode: 409,
errorCode: 'douyin_mashida_credentials_missing',
})
}
try {
await fetchDouyinMashidaOrderDetail({
shopOrderId: '0',
config,
})
return {
ok: true,
message: '网关连通且请求成功',
}
} catch (error: unknown) {
const errorMsg = error instanceof Error ? error.message : String(error)
if (errorMsg.includes('签名') || errorMsg.includes('sign')) {
throw createHttpError(`签名验证失败: ${errorMsg}`, {
statusCode: 400,
errorCode: 'signature_failed',
})
}
// 平台先验签名,出现非签名错误(如订单不存在)说明签名和网络完全正常
return {
ok: true,
message: `网关连通 + 签名校验通过(平台返回业务响应: ${errorMsg})`,
}
}
}
function mapAdminDouyinMashidaConfig(config: DouyinMashidaSourceConfig) {
return {
enabled: config.enabled,
@@ -82,3 +184,4 @@ export function isMaskedSecretValue(value: unknown, currentSecret = ''): boolean
}
return false
}
@@ -146,6 +146,35 @@ export async function sendDouyinMashidaOrderMessage(
})
}
/** 消息推送配置(向平台订阅/停用推送):对应开放平台 message.config */
export async function configureDouyinMashidaMessage(
input: {
status: number
messageUrl?: string | undefined
topics?: string | undefined
} & DouyinMashidaCallOptions,
): Promise<JsonObject> {
const params: Record<string, string> = {
status: String(input.status),
}
if (input.messageUrl) {
params.message_url = input.messageUrl
}
if (input.topics) {
params.topics = input.topics
}
const response = await douyinMashidaGatewayRequest({
method: 'message.config',
params,
config: input.config,
timeoutMs: input.timeoutMs,
})
return isPlainObject(response) ? response : {}
}
function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === '[object Object]'
}