feat(douyin-mashida): 彻底重构优化配置面板,支持一键填域名/订阅推送/网关自检并剔除冗余字段
- 剔除历史旧卡券时代废弃的 afterVerifyLink(去使用链接)与 afterVerifyMessage(通知正文) - 面板新增一键填入当前域名、向开放平台一键订阅推送、停用推送以及网关连通与签名自检功能 - 业务配置重构为「基础接入与商户凭据」、「买家 H5 直充与发货私信」、「高级网关设置」三层清晰结构 - 后端与前端服务新增 /douyin-mashida/subscribe 与 /douyin-mashida/test-gateway 统一接口 验证方式:前端类型检查与打包构建通过,全量 132 项前端单测与 674 项后端单测全部通过
This commit is contained in:
@@ -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]'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user