From 983ba47dbf7707b605cc6374c3ddf655eeec79ae Mon Sep 17 00:00:00 2001 From: zhuotianyuan Date: Wed, 7 Jan 2026 11:54:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(adapter):=20=E5=85=AC=E5=BC=80=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E6=96=B9=E6=B3=95=E4=BB=A5=E6=94=AF=E6=8C=81=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=8E=A5=E5=8F=A3=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将各适配器中的私有映射方法改为公开,并在接口中定义统一方法签名 修改webhook控制器以使用适配器映射方法处理订单数据 --- src/adapter/shopyy.adapter.ts | 18 ++--- src/adapter/woocommerce.adapter.ts | 22 +++--- src/controller/webhook.controller.ts | 97 +++++++++++-------------- src/interface/site-adapter.interface.ts | 6 ++ 4 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/adapter/shopyy.adapter.ts b/src/adapter/shopyy.adapter.ts index 33d1e61..10c51fb 100644 --- a/src/adapter/shopyy.adapter.ts +++ b/src/adapter/shopyy.adapter.ts @@ -50,7 +50,7 @@ export class ShopyyAdapter implements ISiteAdapter { // this.mapSubscription = this.mapSubscription.bind(this); } - private mapMedia(item: any): UnifiedMediaDTO { + mapMedia(item: any): UnifiedMediaDTO { // 映射媒体项目 return { id: item.id, @@ -63,7 +63,7 @@ export class ShopyyAdapter implements ISiteAdapter { }; } - private mapMediaSearchParams(params: UnifiedSearchParamsDTO): any { + mapMediaSearchParams(params: UnifiedSearchParamsDTO): any { const { search, page, per_page } = params; const shopyyParams: any = { page: page || 1, @@ -77,7 +77,7 @@ export class ShopyyAdapter implements ISiteAdapter { return shopyyParams; } - private mapProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO { + mapProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO { // 映射产品状态 function mapProductStatus(status: number) { return status === 1 ? 'publish' : 'draft'; @@ -129,7 +129,7 @@ export class ShopyyAdapter implements ISiteAdapter { }; } - private mapVariation(variant: ShopyyVariant): UnifiedProductVariationDTO { + mapVariation(variant: ShopyyVariant): UnifiedProductVariationDTO { // 映射变体 return { id: variant.id, @@ -152,7 +152,7 @@ export class ShopyyAdapter implements ISiteAdapter { [180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed [190]: OrderStatus.CANCEL // 190 取消 转为 cancelled } - private mapOrder(item: ShopyyOrder): UnifiedOrderDTO { + mapOrder(item: ShopyyOrder): UnifiedOrderDTO { // 提取账单和送货地址 如果不存在则为空对象 const billing = (item as any).billing_address || {}; const shipping = (item as any).shipping_address || {}; @@ -333,7 +333,7 @@ export class ShopyyAdapter implements ISiteAdapter { // 确认发货 } - private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO { + mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO { // 处理多地址结构 const addresses = item.addresses || []; const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {}); @@ -726,7 +726,7 @@ export class ShopyyAdapter implements ISiteAdapter { return this.mapReview(review); } - private mapReview(review: any): UnifiedReviewDTO { + mapReview(review: any): UnifiedReviewDTO { // 将ShopYY评论数据映射到统一评论DTO格式 return { id: review.id || review.review_id, @@ -743,7 +743,7 @@ export class ShopyyAdapter implements ISiteAdapter { }; } - private mapReviewSearchParams(params: UnifiedSearchParamsDTO): any { + mapReviewSearchParams(params: UnifiedSearchParamsDTO): any { const { search, page, per_page, where } = params; const shopyyParams: any = { page: page || 1, @@ -780,7 +780,7 @@ export class ShopyyAdapter implements ISiteAdapter { } // Webhook相关方法 - private mapWebhook(item: ShopyyWebhook): UnifiedWebhookDTO { + mapWebhook(item: ShopyyWebhook): UnifiedWebhookDTO { return { id: item.id, name: item.webhook_name || `Webhook-${item.id}`, diff --git a/src/adapter/woocommerce.adapter.ts b/src/adapter/woocommerce.adapter.ts index badf467..c407780 100644 --- a/src/adapter/woocommerce.adapter.ts +++ b/src/adapter/woocommerce.adapter.ts @@ -42,7 +42,7 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 映射 WooCommerce webhook 到统一格式 - private mapWebhook(webhook: WooWebhook): UnifiedWebhookDTO { + mapWebhook(webhook: WooWebhook): UnifiedWebhookDTO { return { id: webhook.id.toString(), name: webhook.name, @@ -169,7 +169,7 @@ export class WooCommerceAdapter implements ISiteAdapter { - private mapProductSearchParams(params: UnifiedSearchParamsDTO): Partial { + mapProductSearchParams(params: UnifiedSearchParamsDTO): Partial { const page = Number(params.page ?? 1); const per_page = Number(params.per_page ?? 20); const where = params.where && typeof params.where === 'object' ? params.where : {}; @@ -225,7 +225,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return mapped; } - private mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial { + mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial { // 计算分页参数 const page = Number(params.page ?? 1); const per_page = Number(params.per_page ?? 20); @@ -293,7 +293,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return mapped; } - private mapCustomerSearchParams(params: UnifiedSearchParamsDTO): Record { + mapCustomerSearchParams(params: UnifiedSearchParamsDTO): Record { const page = Number(params.page ?? 1); const per_page = Number(params.per_page ?? 20); const where = params.where && typeof params.where === 'object' ? params.where : {}; @@ -346,7 +346,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return mapped; } - private mapProduct(item: WooProduct): UnifiedProductDTO { + mapProduct(item: WooProduct): UnifiedProductDTO { // 将 WooCommerce 产品数据映射为统一产品DTO // 保留常用字段与时间信息以便前端统一展示 // https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties @@ -449,7 +449,7 @@ export class WooCommerceAdapter implements ISiteAdapter { addr.phone ].filter(Boolean).join(', '); } - private mapOrder(item: WooOrder): UnifiedOrderDTO { + mapOrder(item: WooOrder): UnifiedOrderDTO { // 将 WooCommerce 订单数据映射为统一订单DTO // 包含账单地址与收货地址以及创建与更新时间 @@ -502,7 +502,7 @@ export class WooCommerceAdapter implements ISiteAdapter { }; } - private mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO { + mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO { // 将 WooCommerce 订阅数据映射为统一订阅DTO // 若缺少创建时间则回退为开始时间 return { @@ -520,7 +520,7 @@ export class WooCommerceAdapter implements ISiteAdapter { }; } - private mapMedia(item: WpMedia): UnifiedMediaDTO { + mapMedia(item: WpMedia): UnifiedMediaDTO { // 将 WordPress 媒体数据映射为统一媒体DTO // 兼容不同字段命名的时间信息 return { @@ -866,7 +866,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return media.map((mediaItem: any) => this.mapMedia(mediaItem)); } - private mapReview(item: any): UnifiedReviewDTO & { raw: any } { + mapReview(item: any): UnifiedReviewDTO & { raw: any } { // 将 WooCommerce 评论数据映射为统一评论DTO return { id: item.id, @@ -939,7 +939,7 @@ export class WooCommerceAdapter implements ISiteAdapter { return result as any; } - private mapCustomer(item: WooCustomer): UnifiedCustomerDTO { + mapCustomer(item: WooCustomer): UnifiedCustomerDTO { // 将 WooCommerce 客户数据映射为统一客户DTO // 包含基础信息地址信息与时间信息 return { @@ -1070,7 +1070,7 @@ export class WooCommerceAdapter implements ISiteAdapter { } // 映射 WooCommerce 变体到统一格式 - private mapVariation(variation: any, productName?: string): UnifiedProductVariationDTO { + mapVariation(variation: any, productName?: string): UnifiedProductVariationDTO { // 将变体属性转换为统一格式 const mappedAttributes = variation.attributes && Array.isArray(variation.attributes) ? variation.attributes.map((attr: any) => ({ diff --git a/src/controller/webhook.controller.ts b/src/controller/webhook.controller.ts index 41ef422..37fcc5a 100644 --- a/src/controller/webhook.controller.ts +++ b/src/controller/webhook.controller.ts @@ -9,13 +9,10 @@ import { } from '@midwayjs/decorator'; import { Context } from '@midwayjs/koa'; import * as crypto from 'crypto'; - + import { SiteService } from '../service/site.service'; import { OrderService } from '../service/order.service'; - -import { - UnifiedOrderDTO, -} from '../dto/site-api.dto'; +import { SiteApiService } from '../service/site-api.service'; @Controller('/webhook') export class WebhookController { @@ -31,9 +28,11 @@ export class WebhookController { @Logger() logger: ILogger; - + @Inject() private readonly siteService: SiteService; + @Inject() + private readonly siteApiService: SiteApiService; // 移除配置中的站点数组,来源统一改为数据库 @@ -49,7 +48,7 @@ export class WebhookController { @Query('siteId') siteIdStr: string, @Headers() header: any ) { - console.log(`webhook woocommerce`, siteIdStr, body,header) + console.log(`webhook woocommerce`, siteIdStr, body, header) const signature = header['x-wc-webhook-signature']; const topic = header['x-wc-webhook-topic']; const source = header['x-wc-webhook-source']; @@ -79,43 +78,44 @@ export class WebhookController { .update(rawBody) .digest('base64'); try { - if (hash === signature) { - switch (topic) { - case 'product.created': - case 'product.updated': - // 不再写入本地,平台事件仅确认接收 - break; - case 'product.deleted': - // 不再写入本地,平台事件仅确认接收 - break; - case 'order.created': - case 'order.updated': - await this.orderService.syncSingleOrder(siteId, body); - break; - case 'order.deleted': - break; - case 'customer.created': - break; - case 'customer.updated': - break; - case 'customer.deleted': - break; - default: - console.log('Unhandled event:', body.event); - } - - return { - code: 200, - success: true, - message: 'Webhook processed successfully', - }; - } else { + if (hash !== signature) { return { code: 403, success: false, message: 'Webhook verification failed', }; } + const adapter = await this.siteApiService.getAdapter(siteId); + switch (topic) { + case 'product.created': + case 'product.updated': + // 不再写入本地,平台事件仅确认接收 + break; + case 'product.deleted': + // 不再写入本地,平台事件仅确认接收 + break; + case 'order.created': + case 'order.updated': + const order = adapter.mapOrder(body) + await this.orderService.syncSingleOrder(siteId, order); + break; + case 'order.deleted': + break; + case 'customer.created': + break; + case 'customer.updated': + break; + case 'customer.deleted': + break; + default: + console.log('Unhandled event:', body.event); + + return { + code: 200, + success: true, + message: 'Webhook processed successfully', + }; + } } catch (error) { console.log(error); } @@ -130,23 +130,10 @@ export class WebhookController { @Query('signature') signature: string, @Headers() header: any ) { + console.log(`webhook shoppy`, siteIdStr, body, header) const topic = header['x-oemsaas-event-type']; - // const source = header['x-oemsaas-shop-domain']; + // const source = header['x-oemsaas-shop-domain']; const siteId = Number(siteIdStr); - const bodys = new UnifiedOrderDTO(); - Object.assign(bodys, body); - // 从数据库获取站点配置 - const site = await this.siteService.get(siteId, true); - - // if (!site || !source?.includes(site.websiteUrl)) { - if (!site) { - console.log('domain not match'); - return { - code: HttpStatus.BAD_REQUEST, - success: false, - message: 'domain not match', - }; - } if (!signature) { return { @@ -162,6 +149,7 @@ export class WebhookController { // .createHmac('sha256', this.secret) // .update(rawBody) // .digest('base64'); + const adapter = await this.siteApiService.getAdapter(siteId); try { if (this.secret === signature) { switch (topic) { @@ -174,7 +162,8 @@ export class WebhookController { break; case 'orders/create': case 'orders/update': - await this.orderService.syncSingleOrder(siteId, bodys); + const order = adapter.mapOrder(body) + await this.orderService.syncSingleOrder(siteId, order); break; case 'orders/delete': break; diff --git a/src/interface/site-adapter.interface.ts b/src/interface/site-adapter.interface.ts index 2e02cc3..9baf0c7 100644 --- a/src/interface/site-adapter.interface.ts +++ b/src/interface/site-adapter.interface.ts @@ -20,6 +20,12 @@ import { UnifiedPaginationDTO, UnifiedSearchParamsDTO } from '../dto/api.dto'; import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto'; export interface ISiteAdapter { + mapOrder(order: any): UnifiedOrderDTO; + mapWebhook(webhook:any):UnifiedWebhookDTO; + mapProduct(product:any): UnifiedProductDTO; + mapReview(data: any): UnifiedReviewDTO; + mapCustomer(data: any): UnifiedCustomerDTO; + mapMedia(data: any): UnifiedMediaDTO; /** * 获取产品列表 */