forked from yoone/API
refactor(站点管理): 移除配置中的站点数组,统一通过数据库获取站点信息
重构多个控制器和服务,将硬编码的站点配置替换为通过 SiteService 从数据库获取 使用批量查询优化站点名称映射,避免 N+1 查询问题 兼容新旧站点数据结构,确保平滑过渡
This commit is contained in:
parent
1d62730ca0
commit
c7480ccc8a
|
|
@ -1,4 +1,4 @@
|
||||||
import { Config, HttpStatus, Inject } from '@midwayjs/core';
|
import { HttpStatus, Inject } from '@midwayjs/core';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Post,
|
Post,
|
||||||
|
|
@ -11,8 +11,8 @@ import { Context } from '@midwayjs/koa';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { WpProductService } from '../service/wp_product.service';
|
import { WpProductService } from '../service/wp_product.service';
|
||||||
import { WPService } from '../service/wp.service';
|
import { WPService } from '../service/wp.service';
|
||||||
|
import { SiteService } from '../service/site.service';
|
||||||
import { OrderService } from '../service/order.service';
|
import { OrderService } from '../service/order.service';
|
||||||
import { WpSite } from '../interface';
|
|
||||||
|
|
||||||
@Controller('/webhook')
|
@Controller('/webhook')
|
||||||
export class WebhookController {
|
export class WebhookController {
|
||||||
|
|
@ -30,8 +30,10 @@ export class WebhookController {
|
||||||
@Inject()
|
@Inject()
|
||||||
ctx: Context;
|
ctx: Context;
|
||||||
|
|
||||||
@Config('wpSite')
|
@Inject()
|
||||||
sites: WpSite[];
|
private readonly siteService: SiteService;
|
||||||
|
|
||||||
|
// 中文注释:移除配置中的站点数组,来源统一改为数据库
|
||||||
|
|
||||||
@Get('/')
|
@Get('/')
|
||||||
async test() {
|
async test() {
|
||||||
|
|
@ -47,7 +49,8 @@ export class WebhookController {
|
||||||
const signature = header['x-wc-webhook-signature'];
|
const signature = header['x-wc-webhook-signature'];
|
||||||
const topic = header['x-wc-webhook-topic'];
|
const topic = header['x-wc-webhook-topic'];
|
||||||
const source = header['x-wc-webhook-source'];
|
const source = header['x-wc-webhook-source'];
|
||||||
let site = this.sites.find(item => item.id === siteId);
|
// 中文注释:从数据库获取站点配置
|
||||||
|
const site = await this.siteService.get(Number(siteId), true);
|
||||||
|
|
||||||
if (!site || !source.includes(site.wpApiUrl)) {
|
if (!site || !source.includes(site.wpApiUrl)) {
|
||||||
console.log('domain not match');
|
console.log('domain not match');
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
Query,
|
Query,
|
||||||
Put,
|
Put,
|
||||||
Body,
|
Body,
|
||||||
Config,
|
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
import { WpProductService } from '../service/wp_product.service';
|
import { WpProductService } from '../service/wp_product.service';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
|
|
@ -20,22 +19,13 @@ import {
|
||||||
UpdateWpProductDTO,
|
UpdateWpProductDTO,
|
||||||
} from '../dto/wp_product.dto';
|
} from '../dto/wp_product.dto';
|
||||||
import { WPService } from '../service/wp.service';
|
import { WPService } from '../service/wp.service';
|
||||||
import { WpSite } from '../interface';
|
import { SiteService } from '../service/site.service';
|
||||||
import {
|
import {
|
||||||
ProductsRes,
|
ProductsRes,
|
||||||
} from '../dto/reponse.dto';
|
} from '../dto/reponse.dto';
|
||||||
@Controller('/wp_product')
|
@Controller('/wp_product')
|
||||||
export class WpProductController {
|
export class WpProductController {
|
||||||
@Inject()
|
// 中文注释:移除控制器内的配置站点引用,统一由服务层处理站点数据
|
||||||
wpService: WPService;
|
|
||||||
|
|
||||||
@Config('wpSite')
|
|
||||||
sites: WpSite[];
|
|
||||||
|
|
||||||
getSite(id: string): WpSite {
|
|
||||||
let idx = this.sites.findIndex(item => item.id === id);
|
|
||||||
return this.sites[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private readonly wpProductService: WpProductService;
|
private readonly wpProductService: WpProductService;
|
||||||
|
|
@ -43,6 +33,9 @@ export class WpProductController {
|
||||||
@Inject()
|
@Inject()
|
||||||
private readonly wpApiService: WPService;
|
private readonly wpApiService: WPService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private readonly siteService: SiteService;
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
type: BooleanRes,
|
type: BooleanRes,
|
||||||
})
|
})
|
||||||
|
|
@ -127,7 +120,7 @@ export class WpProductController {
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
return errorResponse('SKU已存在');
|
return errorResponse('SKU已存在');
|
||||||
}
|
}
|
||||||
const site = await this.wpProductService.getSite(siteId);
|
const site = await this.siteService.get(Number(siteId), true);
|
||||||
const result = await this.wpApiService.updateProduct(
|
const result = await this.wpApiService.updateProduct(
|
||||||
site,
|
site,
|
||||||
productId,
|
productId,
|
||||||
|
|
@ -167,7 +160,7 @@ export class WpProductController {
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
return errorResponse('SKU已存在');
|
return errorResponse('SKU已存在');
|
||||||
}
|
}
|
||||||
const site = await this.wpProductService.getSite(siteId);
|
const site = await this.siteService.get(Number(siteId), true);
|
||||||
const result = await this.wpApiService.updateVariation(
|
const result = await this.wpApiService.updateVariation(
|
||||||
site,
|
site,
|
||||||
productId,
|
productId,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Config, Inject, Provide, sleep } from '@midwayjs/core';
|
import { Inject, Provide, sleep } from '@midwayjs/core';
|
||||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
import { Service } from '../entity/service.entity';
|
import { Service } from '../entity/service.entity';
|
||||||
import { In, IsNull, Like, Repository } from 'typeorm';
|
import { In, IsNull, Like, Repository } from 'typeorm';
|
||||||
|
|
@ -21,7 +21,6 @@ import { StockRecord } from '../entity/stock_record.entity';
|
||||||
import { Stock } from '../entity/stock.entity';
|
import { Stock } from '../entity/stock.entity';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { WPService } from './wp.service';
|
import { WPService } from './wp.service';
|
||||||
import { WpSite } from '../interface';
|
|
||||||
// import { Product } from '../entity/product.entty';
|
// import { Product } from '../entity/product.entty';
|
||||||
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
||||||
import { CanadaPostService } from './canadaPost.service';
|
import { CanadaPostService } from './canadaPost.service';
|
||||||
|
|
@ -31,6 +30,7 @@ import { UniExpressService } from './uni_express.service';
|
||||||
import { StockPoint } from '../entity/stock_point.entity';
|
import { StockPoint } from '../entity/stock_point.entity';
|
||||||
import { OrderService } from './order.service';
|
import { OrderService } from './order.service';
|
||||||
import { convertKeysFromCamelToSnake } from '../utils/object-transform.util';
|
import { convertKeysFromCamelToSnake } from '../utils/object-transform.util';
|
||||||
|
import { SiteService } from './site.service';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class LogisticsService {
|
export class LogisticsService {
|
||||||
|
|
@ -82,13 +82,8 @@ export class LogisticsService {
|
||||||
@Inject()
|
@Inject()
|
||||||
dataSourceManager: TypeORMDataSourceManager;
|
dataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
@Config('wpSite')
|
@Inject()
|
||||||
sites: WpSite[];
|
private readonly siteService: SiteService;
|
||||||
|
|
||||||
getSite(id: string): WpSite {
|
|
||||||
let idx = this.sites.findIndex(item => item.id === id);
|
|
||||||
return this.sites[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
async getServiceList(param: QueryServiceDTO) {
|
async getServiceList(param: QueryServiceDTO) {
|
||||||
const { pageSize, current, carrier_name, isActive } = param;
|
const { pageSize, current, carrier_name, isActive } = param;
|
||||||
|
|
@ -263,7 +258,7 @@ export class LogisticsService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 同步订单状态到woocommerce
|
// 同步订单状态到woocommerce
|
||||||
const site = await this.getSite(order.siteId);
|
const site = await this.siteService.get(Number(order.siteId), true);
|
||||||
if (order.status === OrderStatus.COMPLETED) {
|
if (order.status === OrderStatus.COMPLETED) {
|
||||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
status: OrderStatus.PROCESSING,
|
status: OrderStatus.PROCESSING,
|
||||||
|
|
@ -367,7 +362,7 @@ export class LogisticsService {
|
||||||
const tracking_provider = 'UniUni'; // todo: id未确定,后写进常数
|
const tracking_provider = 'UniUni'; // todo: id未确定,后写进常数
|
||||||
|
|
||||||
// 同步物流信息到woocommerce
|
// 同步物流信息到woocommerce
|
||||||
const site = await this.getSite(order.siteId);
|
const site = await this.siteService.get(Number(order.siteId), true);
|
||||||
const res = await this.wpService.createShipment(site, order.externalOrderId, {
|
const res = await this.wpService.createShipment(site, order.externalOrderId, {
|
||||||
tracking_number: resShipmentOrder.data.tno,
|
tracking_number: resShipmentOrder.data.tno,
|
||||||
tracking_provider: tracking_provider,
|
tracking_provider: tracking_provider,
|
||||||
|
|
@ -493,7 +488,7 @@ export class LogisticsService {
|
||||||
const order = await this.orderModel.findOneBy({
|
const order = await this.orderModel.findOneBy({
|
||||||
id: orderShipment.order_id,
|
id: orderShipment.order_id,
|
||||||
});
|
});
|
||||||
const site = this.getSite(order.siteId);
|
const site = await this.siteService.get(Number(order.siteId), true);
|
||||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
status: OrderStatus.COMPLETED,
|
status: OrderStatus.COMPLETED,
|
||||||
});
|
});
|
||||||
|
|
@ -563,7 +558,10 @@ export class LogisticsService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const siteMap = new Map(this.sites.map(site => [site.id, site.siteName]));
|
// 中文注释:从数据库批量获取站点信息,构建映射以避免 N+1 查询
|
||||||
|
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean)));
|
||||||
|
const { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
|
||||||
|
const siteMap = new Map(sites.map((s: any) => [String(s.id), s.siteName]));
|
||||||
|
|
||||||
return orders.map(order => ({
|
return orders.map(order => ({
|
||||||
...order,
|
...order,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Config, Inject, Provide } from '@midwayjs/core';
|
import { Inject, Provide } from '@midwayjs/core';
|
||||||
import { WPService } from './wp.service';
|
import { WPService } from './wp.service';
|
||||||
|
import { WpSite } from '../interface';
|
||||||
import { Order } from '../entity/order.entity';
|
import { Order } from '../entity/order.entity';
|
||||||
import { In, Like, Repository } from 'typeorm';
|
import { In, Like, Repository } from 'typeorm';
|
||||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
|
|
@ -27,7 +28,7 @@ import dayjs = require('dayjs');
|
||||||
import { OrderDetailRes } from '../dto/reponse.dto';
|
import { OrderDetailRes } from '../dto/reponse.dto';
|
||||||
import { OrderNote } from '../entity/order_note.entity';
|
import { OrderNote } from '../entity/order_note.entity';
|
||||||
import { User } from '../entity/user.entity';
|
import { User } from '../entity/user.entity';
|
||||||
import { WpSite } from '../interface';
|
import { SiteService } from './site.service';
|
||||||
import { ShipmentItem } from '../entity/shipment_item.entity';
|
import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||||
import { UpdateStockDTO } from '../dto/stock.dto';
|
import { UpdateStockDTO } from '../dto/stock.dto';
|
||||||
import { StockService } from './stock.service';
|
import { StockService } from './stock.service';
|
||||||
|
|
@ -35,8 +36,6 @@ import { OrderSaleOriginal } from '../entity/order_item_original.entity';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
@Config('wpSite')
|
|
||||||
sites: WpSite[];
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
wpService: WPService;
|
wpService: WPService;
|
||||||
|
|
@ -101,6 +100,9 @@ export class OrderService {
|
||||||
@InjectEntityModel(Customer)
|
@InjectEntityModel(Customer)
|
||||||
customerModel: Repository<Customer>;
|
customerModel: Repository<Customer>;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
siteService: SiteService;
|
||||||
|
|
||||||
async syncOrders(siteId: string) {
|
async syncOrders(siteId: string) {
|
||||||
const orders = await this.wpService.getOrders(siteId); // 调用 WooCommerce API 获取订单
|
const orders = await this.wpService.getOrders(siteId); // 调用 WooCommerce API 获取订单
|
||||||
for (const order of orders) {
|
for (const order of orders) {
|
||||||
|
|
@ -127,12 +129,9 @@ export class OrderService {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const site = this.sites.find(v => v.id === siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
if (!site) {
|
// 中文注释:将订单状态同步到 WooCommerce,然后切换至下一状态
|
||||||
throw new Error(`更新订单信息,但失败,原因为 ${siteId} 的站点信息不存在`)
|
await this.wpService.updateOrder(site, String(order.id), { status: order.status });
|
||||||
}
|
|
||||||
// 同步更新回 wordpress 的 order 状态
|
|
||||||
await this.wpService.updateOrder(site, order.id, { status: order.status });
|
|
||||||
order.status = this.orderAutoNextStatusMap[originStatus];
|
order.status = this.orderAutoNextStatusMap[originStatus];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新订单状态失败,原因为:', error)
|
console.error('更新订单状态失败,原因为:', error)
|
||||||
|
|
@ -1262,7 +1261,7 @@ export class OrderService {
|
||||||
}
|
}
|
||||||
async getOrderDetail(id: number): Promise<OrderDetailRes> {
|
async getOrderDetail(id: number): Promise<OrderDetailRes> {
|
||||||
const order = await this.orderModel.findOne({ where: { id } });
|
const order = await this.orderModel.findOne({ where: { id } });
|
||||||
const site = this.sites.find(site => site.id === order.siteId);
|
const site = await this.siteService.get(Number(order.siteId), true);
|
||||||
const items = await this.orderItemModel.find({ where: { orderId: id } });
|
const items = await this.orderItemModel.find({ where: { orderId: id } });
|
||||||
const sales = await this.orderSaleModel.find({ where: { orderId: id } });
|
const sales = await this.orderSaleModel.find({ where: { orderId: id } });
|
||||||
const refunds = await this.orderRefundModel.find({
|
const refunds = await this.orderRefundModel.find({
|
||||||
|
|
@ -1415,20 +1414,22 @@ export class OrderService {
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return orders.map(order => {
|
// 中文注释:批量获取订单涉及的站点名称,避免使用配置文件
|
||||||
return {
|
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean)));
|
||||||
|
const { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
|
||||||
|
const siteMap = new Map(sites.map((s: any) => [String(s.id), s.siteName]));
|
||||||
|
return orders.map(order => ({
|
||||||
externalOrderId: order.externalOrderId,
|
externalOrderId: order.externalOrderId,
|
||||||
id: order.id,
|
id: order.id,
|
||||||
siteName:
|
siteName: siteMap.get(order.siteId) || '',
|
||||||
this.sites.find(site => site.id === order.siteId)?.siteName || '',
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelOrder(id: number) {
|
async cancelOrder(id: number) {
|
||||||
const order = await this.orderModel.findOne({ where: { id } });
|
const order = await this.orderModel.findOne({ where: { id } });
|
||||||
if (!order) throw new Error(`订单 ${id}不存在`);
|
if (!order) throw new Error(`订单 ${id}不存在`);
|
||||||
const site = this.wpService.geSite(order.siteId);
|
const s: any = await this.siteService.get(Number(order.siteId), true);
|
||||||
|
const site = { id: String(s.id), wpApiUrl: s.apiUrl, consumerKey: s.consumerKey, consumerSecret: s.consumerSecret, siteName: s.siteName, email: '', emailPswd: '' } as WpSite;
|
||||||
if (order.status !== OrderStatus.CANCEL) {
|
if (order.status !== OrderStatus.CANCEL) {
|
||||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
status: OrderStatus.CANCEL,
|
status: OrderStatus.CANCEL,
|
||||||
|
|
@ -1442,7 +1443,7 @@ export class OrderService {
|
||||||
async refundOrder(id: number) {
|
async refundOrder(id: number) {
|
||||||
const order = await this.orderModel.findOne({ where: { id } });
|
const order = await this.orderModel.findOne({ where: { id } });
|
||||||
if (!order) throw new Error(`订单 ${id}不存在`);
|
if (!order) throw new Error(`订单 ${id}不存在`);
|
||||||
const site = this.wpService.geSite(order.siteId);
|
const site = await this.siteService.get(Number(order.siteId), true);
|
||||||
if (order.status !== OrderStatus.REFUNDED) {
|
if (order.status !== OrderStatus.REFUNDED) {
|
||||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
status: OrderStatus.REFUNDED,
|
status: OrderStatus.REFUNDED,
|
||||||
|
|
@ -1456,7 +1457,7 @@ export class OrderService {
|
||||||
async completedOrder(id: number) {
|
async completedOrder(id: number) {
|
||||||
const order = await this.orderModel.findOne({ where: { id } });
|
const order = await this.orderModel.findOne({ where: { id } });
|
||||||
if (!order) throw new Error(`订单 ${id}不存在`);
|
if (!order) throw new Error(`订单 ${id}不存在`);
|
||||||
const site = this.wpService.geSite(order.siteId);
|
const site = await this.siteService.get(order.siteId);
|
||||||
if (order.status !== OrderStatus.COMPLETED) {
|
if (order.status !== OrderStatus.COMPLETED) {
|
||||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
status: OrderStatus.COMPLETED,
|
status: OrderStatus.COMPLETED,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { Config, Provide } from '@midwayjs/core';
|
import { Inject, Provide } from '@midwayjs/core';
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import WooCommerceRestApi, { WooCommerceRestApiVersion } from '@woocommerce/woocommerce-rest-api';
|
import WooCommerceRestApi, { WooCommerceRestApiVersion } from '@woocommerce/woocommerce-rest-api';
|
||||||
import { WpSite } from '../interface';
|
|
||||||
import { WpProduct } from '../entity/wp_product.entity';
|
import { WpProduct } from '../entity/wp_product.entity';
|
||||||
import { Variation } from '../entity/variation.entity';
|
import { Variation } from '../entity/variation.entity';
|
||||||
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
|
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
|
||||||
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
||||||
|
import { SiteService } from './site.service';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class WPService {
|
export class WPService {
|
||||||
@Config('wpSite')
|
@Inject()
|
||||||
sites: WpSite[];
|
private readonly siteService: SiteService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建 URL,自动规范各段的斜杠,避免出现多 / 或少 / 导致请求失败
|
* 构建 URL,自动规范各段的斜杠,避免出现多 / 或少 / 导致请求失败
|
||||||
|
|
@ -35,12 +35,12 @@ export class WPService {
|
||||||
* @param site 站点配置
|
* @param site 站点配置
|
||||||
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
||||||
*/
|
*/
|
||||||
private createApi(site: WpSite, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
||||||
|
const url = site.wpApiUrl ?? site.apiUrl; // 中文注释:兼容数据库 Site 与 WpSite 结构
|
||||||
return new WooCommerceRestApi({
|
return new WooCommerceRestApi({
|
||||||
url: site.wpApiUrl,
|
url,
|
||||||
consumerKey: site.consumerKey,
|
consumerKey: site.consumerKey,
|
||||||
consumerSecret: site.consumerSecret,
|
consumerSecret: site.consumerSecret,
|
||||||
// SDK 的版本字段有联合类型限制,这里兼容插件命名空间(例如 wcs/v1)
|
|
||||||
version: namespace,
|
version: namespace,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -78,20 +78,18 @@ export class WPService {
|
||||||
* @param consumerSecret WooCommerce 的消费者密钥
|
* @param consumerSecret WooCommerce 的消费者密钥
|
||||||
*/
|
*/
|
||||||
|
|
||||||
geSite(id: string): WpSite {
|
|
||||||
let idx = this.sites.findIndex(item => item.id === id);
|
|
||||||
return this.sites[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchData<T>(
|
async fetchData<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
site: WpSite,
|
site: any,
|
||||||
param: Record<string, any> = {}
|
param: Record<string, any> = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
try {
|
try {
|
||||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
|
const { consumerKey, consumerSecret } = site;
|
||||||
// 构建 URL,规避多/或少/问题
|
// 构建 URL,规避多/或少/问题
|
||||||
const url = this.buildURL(wpApiUrl, '/wp-json', endpoint);
|
const url = this.buildURL(apiUrl, '/wp-json', endpoint);
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
|
|
@ -111,22 +109,22 @@ export class WPService {
|
||||||
|
|
||||||
async fetchPagedData<T>(
|
async fetchPagedData<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
site: WpSite,
|
site: any,
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
perPage: number = 100
|
perPage: number = 100
|
||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const allData: T[] = [];
|
const allData: T[] = [];
|
||||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
const { apiUrl, consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
console.log(`!!!wpApiUrl, consumerKey, consumerSecret, auth`,wpApiUrl, consumerKey, consumerSecret, auth)
|
console.log(`!!!wpApiUrl, consumerKey, consumerSecret, auth`,site.apiUrl, consumerKey, consumerSecret, auth)
|
||||||
let hasMore = true;
|
let hasMore = true;
|
||||||
while (hasMore) {
|
while (hasMore) {
|
||||||
const config: AxiosRequestConfig = {
|
const config: AxiosRequestConfig = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
// 构建 URL,规避多/或少/问题
|
// 构建 URL,规避多/或少/问题
|
||||||
url: this.buildURL(wpApiUrl, '/wp-json', endpoint),
|
url: this.buildURL(apiUrl, '/wp-json', endpoint),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Basic ${auth}`,
|
Authorization: `Basic ${auth}`,
|
||||||
},
|
},
|
||||||
|
|
@ -156,18 +154,18 @@ export class WPService {
|
||||||
return allData;
|
return allData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProducts(site: WpSite): Promise<WpProduct[]> {
|
async getProducts(site: any): Promise<WpProduct[]> {
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetAll<WpProduct>(api, 'products');
|
return await this.sdkGetAll<WpProduct>(api, 'products');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVariations(site: WpSite, productId: number): Promise<Variation[]> {
|
async getVariations(site: any, productId: number): Promise<Variation[]> {
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetAll<Variation>(api, `products/${productId}/variations`);
|
return await this.sdkGetAll<Variation>(api, `products/${productId}/variations`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVariation(
|
async getVariation(
|
||||||
site: WpSite,
|
site: any,
|
||||||
productId: number,
|
productId: number,
|
||||||
variationId: number
|
variationId: number
|
||||||
): Promise<Variation> {
|
): Promise<Variation> {
|
||||||
|
|
@ -180,13 +178,13 @@ export class WPService {
|
||||||
siteId: string,
|
siteId: string,
|
||||||
orderId: string
|
orderId: string
|
||||||
): Promise<Record<string, any>> {
|
): Promise<Record<string, any>> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${orderId}`);
|
const res = await api.get(`orders/${orderId}`);
|
||||||
return res.data as Record<string, any>;
|
return res.data as Record<string, any>;
|
||||||
}
|
}
|
||||||
async getOrders(siteId: string): Promise<Record<string, any>[]> {
|
async getOrders(siteId: string): Promise<Record<string, any>[]> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetAll<Record<string, any>>(api, 'orders');
|
return await this.sdkGetAll<Record<string, any>>(api, 'orders');
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +195,7 @@ export class WPService {
|
||||||
* 返回所有分页合并后的订阅数组。
|
* 返回所有分页合并后的订阅数组。
|
||||||
*/
|
*/
|
||||||
async getSubscriptions(siteId: string): Promise<Record<string, any>[]> {
|
async getSubscriptions(siteId: string): Promise<Record<string, any>[]> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
// 优先使用 Subscriptions 命名空间 wcs/v1,失败回退 wc/v3
|
// 优先使用 Subscriptions 命名空间 wcs/v1,失败回退 wc/v3
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetAll<Record<string, any>>(api, 'subscriptions');
|
return await this.sdkGetAll<Record<string, any>>(api, 'subscriptions');
|
||||||
|
|
@ -209,7 +207,7 @@ export class WPService {
|
||||||
orderId: string,
|
orderId: string,
|
||||||
refundId: number
|
refundId: number
|
||||||
): Promise<Record<string, any>> {
|
): Promise<Record<string, any>> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${orderId}/refunds/${refundId}`);
|
const res = await api.get(`orders/${orderId}/refunds/${refundId}`);
|
||||||
return res.data as Record<string, any>;
|
return res.data as Record<string, any>;
|
||||||
|
|
@ -219,7 +217,7 @@ export class WPService {
|
||||||
siteId: string,
|
siteId: string,
|
||||||
orderId: number
|
orderId: number
|
||||||
): Promise<Record<string, any>[]> {
|
): Promise<Record<string, any>[]> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/refunds`);
|
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/refunds`);
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +227,7 @@ export class WPService {
|
||||||
orderId: number,
|
orderId: number,
|
||||||
noteId: number
|
noteId: number
|
||||||
): Promise<Record<string, any>> {
|
): Promise<Record<string, any>> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${orderId}/notes/${noteId}`);
|
const res = await api.get(`orders/${orderId}/notes/${noteId}`);
|
||||||
return res.data as Record<string, any>;
|
return res.data as Record<string, any>;
|
||||||
|
|
@ -239,24 +237,25 @@ export class WPService {
|
||||||
siteId: string,
|
siteId: string,
|
||||||
orderId: number
|
orderId: number
|
||||||
): Promise<Record<string, any>[]> {
|
): Promise<Record<string, any>[]> {
|
||||||
const site = this.geSite(siteId);
|
const site = await this.siteService.get(siteId);
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/notes`);
|
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/notes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateData<T>(
|
async updateData<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
site: WpSite,
|
site: any,
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
): Promise<Boolean> {
|
): Promise<Boolean> {
|
||||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
|
const { consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
const config: AxiosRequestConfig = {
|
const config: AxiosRequestConfig = {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
// 构建 URL,规避多/或少/问题
|
// 构建 URL,规避多/或少/问题
|
||||||
url: this.buildURL(wpApiUrl, '/wp-json', endpoint),
|
url: this.buildURL(apiUrl, '/wp-json', endpoint),
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Basic ${auth}`,
|
Authorization: `Basic ${auth}`,
|
||||||
},
|
},
|
||||||
|
|
@ -276,7 +275,7 @@ export class WPService {
|
||||||
* @param data 更新的数据
|
* @param data 更新的数据
|
||||||
*/
|
*/
|
||||||
async updateProduct(
|
async updateProduct(
|
||||||
site: WpSite,
|
site: any,
|
||||||
productId: string,
|
productId: string,
|
||||||
data: UpdateWpProductDTO
|
data: UpdateWpProductDTO
|
||||||
): Promise<Boolean> {
|
): Promise<Boolean> {
|
||||||
|
|
@ -295,7 +294,7 @@ export class WPService {
|
||||||
* @param stock_status 上下架状态
|
* @param stock_status 上下架状态
|
||||||
*/
|
*/
|
||||||
async updateProductStatus(
|
async updateProductStatus(
|
||||||
site: WpSite,
|
site: any,
|
||||||
productId: string,
|
productId: string,
|
||||||
status: ProductStatus,
|
status: ProductStatus,
|
||||||
stock_status: ProductStockStatus
|
stock_status: ProductStockStatus
|
||||||
|
|
@ -315,7 +314,7 @@ export class WPService {
|
||||||
* @param data 更新的数据
|
* @param data 更新的数据
|
||||||
*/
|
*/
|
||||||
async updateVariation(
|
async updateVariation(
|
||||||
site: WpSite,
|
site: any,
|
||||||
productId: string,
|
productId: string,
|
||||||
variationId: string,
|
variationId: string,
|
||||||
data: UpdateVariationDTO
|
data: UpdateVariationDTO
|
||||||
|
|
@ -336,7 +335,7 @@ export class WPService {
|
||||||
* 更新 Order
|
* 更新 Order
|
||||||
*/
|
*/
|
||||||
async updateOrder(
|
async updateOrder(
|
||||||
site: WpSite,
|
site: any,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
): Promise<Boolean> {
|
): Promise<Boolean> {
|
||||||
|
|
@ -344,11 +343,12 @@ export class WPService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createShipment(
|
async createShipment(
|
||||||
site: WpSite,
|
site: any,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
) {
|
) {
|
||||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
|
const { consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
|
|
@ -356,7 +356,7 @@ export class WPService {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
// 构建 URL,规避多/或少/问题
|
// 构建 URL,规避多/或少/问题
|
||||||
url: this.buildURL(
|
url: this.buildURL(
|
||||||
wpApiUrl,
|
apiUrl,
|
||||||
'/wp-json',
|
'/wp-json',
|
||||||
'wc-ast/v3/orders',
|
'wc-ast/v3/orders',
|
||||||
orderId,
|
orderId,
|
||||||
|
|
@ -371,11 +371,12 @@ export class WPService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteShipment(
|
async deleteShipment(
|
||||||
site: WpSite,
|
site: any,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
trackingId: string,
|
trackingId: string,
|
||||||
): Promise<Boolean> {
|
): Promise<Boolean> {
|
||||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
const apiUrl = site.wpApiUrl ?? site.apiUrl;
|
||||||
|
const { consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
|
|
@ -386,7 +387,7 @@ export class WPService {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
// 构建 URL,规避多/或少/问题
|
// 构建 URL,规避多/或少/问题
|
||||||
url: this.buildURL(
|
url: this.buildURL(
|
||||||
wpApiUrl,
|
apiUrl,
|
||||||
'/wp-json',
|
'/wp-json',
|
||||||
'wc-ast/v3/orders',
|
'wc-ast/v3/orders',
|
||||||
orderId,
|
orderId,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Product } from '../entity/product.entity';
|
import { Product } from '../entity/product.entity';
|
||||||
import { Config, Inject, Provide } from '@midwayjs/core';
|
import { Inject, Provide } from '@midwayjs/core';
|
||||||
import { WPService } from './wp.service';
|
import { WPService } from './wp.service';
|
||||||
import { WpSite } from '../interface';
|
import { WpSite } from '../interface';
|
||||||
import { WpProduct } from '../entity/wp_product.entity';
|
import { WpProduct } from '../entity/wp_product.entity';
|
||||||
|
|
@ -12,28 +12,38 @@ import {
|
||||||
UpdateWpProductDTO,
|
UpdateWpProductDTO,
|
||||||
} from '../dto/wp_product.dto';
|
} from '../dto/wp_product.dto';
|
||||||
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
||||||
|
import { SiteService } from './site.service';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class WpProductService {
|
export class WpProductService {
|
||||||
@Config('wpSite')
|
// 中文注释:移除配置中的站点数组,统一从数据库获取站点信息
|
||||||
sites: WpSite[];
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private readonly wpApiService: WPService;
|
private readonly wpApiService: WPService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private readonly siteService: SiteService;
|
||||||
|
|
||||||
@InjectEntityModel(WpProduct)
|
@InjectEntityModel(WpProduct)
|
||||||
wpProductModel: Repository<WpProduct>;
|
wpProductModel: Repository<WpProduct>;
|
||||||
|
|
||||||
@InjectEntityModel(Variation)
|
@InjectEntityModel(Variation)
|
||||||
variationModel: Repository<Variation>;
|
variationModel: Repository<Variation>;
|
||||||
|
|
||||||
getSite(id: string): WpSite {
|
|
||||||
let idx = this.sites.findIndex(item => item.id === id);
|
|
||||||
return this.sites[idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAllSites() {
|
async syncAllSites() {
|
||||||
for (const site of this.sites) {
|
// 中文注释:从数据库获取所有启用的站点,并逐站点同步产品与变体
|
||||||
|
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false }, true);
|
||||||
|
for (const s of items as any[]) {
|
||||||
|
const site: WpSite = {
|
||||||
|
id: String(s.id),
|
||||||
|
wpApiUrl: s.apiUrl,
|
||||||
|
consumerKey: s.consumerKey,
|
||||||
|
consumerSecret: s.consumerSecret,
|
||||||
|
siteName: s.siteName,
|
||||||
|
email: '',
|
||||||
|
emailPswd: '',
|
||||||
|
};
|
||||||
const products = await this.wpApiService.getProducts(site);
|
const products = await this.wpApiService.getProducts(site);
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
const variations =
|
const variations =
|
||||||
|
|
@ -46,7 +56,8 @@ export class WpProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncSite(siteId: string) {
|
async syncSite(siteId: string) {
|
||||||
const site = this.getSite(siteId);
|
// 中文注释:通过数据库获取站点并转换为 WpSite,用于后续 WooCommerce 同步
|
||||||
|
const site = await this.siteService.get(Number(siteId), true);
|
||||||
const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product')
|
const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product')
|
||||||
.select([
|
.select([
|
||||||
'wp_product.id ',
|
'wp_product.id ',
|
||||||
|
|
@ -91,7 +102,7 @@ const excludeValues = [];
|
||||||
// 控制产品上下架
|
// 控制产品上下架
|
||||||
async updateProductStatus(id: number, status: ProductStatus, stock_status: ProductStockStatus) {
|
async updateProductStatus(id: number, status: ProductStatus, stock_status: ProductStockStatus) {
|
||||||
const wpProduct = await this.wpProductModel.findOneBy({ id });
|
const wpProduct = await this.wpProductModel.findOneBy({ id });
|
||||||
const site = await this.getSite(wpProduct.siteId);
|
const site = await this.siteService.get(Number(wpProduct.siteId), true);
|
||||||
wpProduct.status = status;
|
wpProduct.status = status;
|
||||||
wpProduct.stockStatus = stock_status;
|
wpProduct.stockStatus = stock_status;
|
||||||
const res = await this.wpApiService.updateProductStatus(site, wpProduct.externalProductId, status, stock_status);
|
const res = await this.wpApiService.updateProductStatus(site, wpProduct.externalProductId, status, stock_status);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue