zksu
/
API
forked from yoone/API
1
0
Fork 0
API/src/service/wp.service.ts

402 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Inject, Provide } from '@midwayjs/core';
import axios, { AxiosRequestConfig } from 'axios';
import WooCommerceRestApi, { WooCommerceRestApiVersion } from '@woocommerce/woocommerce-rest-api';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
import { SiteService } from './site.service';
@Provide()
export class WPService {
@Inject()
private readonly siteService: SiteService;
/**
* 构建 URL自动规范各段的斜杠避免出现多 / 或少 / 导致请求失败
* 使用示例this.buildURL(wpApiUrl, '/wp-json', 'wc/v3/products', productId)
*/
private buildURL(base: string, ...parts: Array<string | number>): string {
// 去掉 base 末尾多余斜杠,但不影响协议中的 //
const baseSanitized = String(base).replace(/\/+$/g, '');
// 规范各段前后斜杠
const segments = parts
.filter((p) => p !== undefined && p !== null)
.map((p) => String(p))
.map((s) => s.replace(/^\/+|\/+$/g, ''))
.filter(Boolean);
const joined = [baseSanitized, ...segments].join('/');
// 折叠除协议外的多余斜杠,例如 https://example.com//a///b -> https://example.com/a/b
return joined.replace(/([^:])\/{2,}/g, '$1/');
}
/**
* 创建 WooCommerce SDK 实例
* @param site 站点配置
* @param namespace API 命名空间,默认 wc/v3订阅推荐 wcs/v1
*/
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
return new WooCommerceRestApi({
url: site.apiUrl,
consumerKey: site.consumerKey,
consumerSecret: site.consumerSecret,
version: namespace,
});
}
/**
* 通过 SDK 获取单页数据,并返回数据与 totalPages
*/
private async sdkGetPage<T>(api: any, resource: string, params: Record<string, any> = {}) {
const page = params.page ?? 1;
const per_page = params.per_page ?? 100;
const res = await api.get(resource.replace(/^\/+/, ''), { ...params, page, per_page });
const data = res.data as T[];
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 1);
return { items: data, totalPages, page, per_page };
}
/**
* 通过 SDK 聚合分页数据,返回全部数据
*/
private async sdkGetAll<T>(api: any, resource: string, params: Record<string, any> = {}, maxPages: number = 50): Promise<T[]> {
const result: T[] = [];
for (let page = 1; page <= maxPages; page++) {
const { items, totalPages } = await this.sdkGetPage<T>(api, resource, { ...params, page });
result.push(...items);
if (page >= totalPages) break;
}
return result;
}
/**
* 获取 WordPress 数据
* @param wpApiUrl WordPress REST API 的基础地址
* @param endpoint API 端点路径(例如 wc/v3/products
* @param consumerKey WooCommerce 的消费者密钥
* @param consumerSecret WooCommerce 的消费者密钥
*/
async fetchData<T>(
endpoint: string,
site: any,
param: Record<string, any> = {}
): Promise<T> {
try {
const apiUrl = site.apiUrl;
const { consumerKey, consumerSecret } = site;
// 构建 URL规避多/或少/问题
const url = this.buildURL(apiUrl, '/wp-json', endpoint);
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
const response = await axios.request({
url,
headers: {
Authorization: `Basic ${auth}`,
},
method: 'GET',
...param,
});
return response.data;
} catch (error) {
throw error;
}
}
async fetchPagedData<T>(
endpoint: string,
site: any,
page: number = 1,
perPage: number = 100
): Promise<T[]> {
const allData: T[] = [];
const { apiUrl, consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
console.log(`!!!wpApiUrl, consumerKey, consumerSecret, auth`,site.apiUrl, consumerKey, consumerSecret, auth)
let hasMore = true;
while (hasMore) {
const config: AxiosRequestConfig = {
method: 'GET',
// 构建 URL规避多/或少/问题
url: this.buildURL(apiUrl, '/wp-json', endpoint),
headers: {
Authorization: `Basic ${auth}`,
},
params: {
page,
per_page: perPage,
},
};
try {
const response = await axios.request(config);
// Append the current page data
allData.push(...response.data);
// Check for more pages
const totalPages = parseInt(
response.headers['x-wp-totalpages'] || '1',
10
);
hasMore = page < totalPages;
page += 1;
} catch (error) {
throw error;
}
}
return allData;
}
async getProducts(site: any): Promise<WpProduct[]> {
const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<WpProduct>(api, 'products');
}
async getVariations(site: any, productId: number): Promise<Variation[]> {
const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<Variation>(api, `products/${productId}/variations`);
}
async getVariation(
site: any,
productId: number,
variationId: number
): Promise<Variation> {
const api = this.createApi(site, 'wc/v3');
const res = await api.get(`products/${productId}/variations/${variationId}`);
return res.data as Variation;
}
async getOrder(
siteId: string,
orderId: string
): Promise<Record<string, any>> {
const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3');
const res = await api.get(`orders/${orderId}`);
return res.data as Record<string, any>;
}
async getOrders(siteId: string): Promise<Record<string, any>[]> {
const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<Record<string, any>>(api, 'orders');
}
/**
* 获取 WooCommerce Subscriptions
* 优先尝试 wc/v1/subscriptionsSubscriptions 插件提供),失败时回退 wc/v3/subscriptions。
* 返回所有分页合并后的订阅数组。
*/
async getSubscriptions(siteId: string): Promise<Record<string, any>[]> {
const site = await this.siteService.get(siteId);
// 优先使用 Subscriptions 命名空间 wcs/v1失败回退 wc/v3
const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<Record<string, any>>(api, 'subscriptions');
}
async getOrderRefund(
siteId: string,
orderId: string,
refundId: number
): Promise<Record<string, any>> {
const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3');
const res = await api.get(`orders/${orderId}/refunds/${refundId}`);
return res.data as Record<string, any>;
}
async getOrderRefunds(
siteId: string,
orderId: number
): Promise<Record<string, any>[]> {
const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/refunds`);
}
async getOrderNote(
siteId: string,
orderId: number,
noteId: number
): Promise<Record<string, any>> {
const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3');
const res = await api.get(`orders/${orderId}/notes/${noteId}`);
return res.data as Record<string, any>;
}
async getOrderNotes(
siteId: string,
orderId: number
): Promise<Record<string, any>[]> {
const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/notes`);
}
async updateData<T>(
endpoint: string,
site: any,
data: Record<string, any>
): Promise<Boolean> {
const apiUrl = site.apiUrl;
const { consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
const config: AxiosRequestConfig = {
method: 'PUT',
// 构建 URL规避多/或少/问题
url: this.buildURL(apiUrl, '/wp-json', endpoint),
headers: {
Authorization: `Basic ${auth}`,
},
data,
};
try {
await axios.request(config);
return true;
} catch (error) {
return false;
}
}
/**
* 更新 WooCommerce 产品
* @param productId 产品 ID
* @param data 更新的数据
*/
async updateProduct(
site: any,
productId: string,
data: UpdateWpProductDTO
): Promise<Boolean> {
const { regular_price, sale_price, ...params } = data;
return await this.updateData(`/wc/v3/products/${productId}`, site, {
...params,
regular_price: regular_price ? regular_price.toString() : null,
sale_price: sale_price ? sale_price.toString() : null,
});
}
/**
* 更新 WooCommerce 产品 上下架状态
* @param productId 产品 ID
* @param status 状态
* @param stock_status 上下架状态
*/
async updateProductStatus(
site: any,
productId: string,
status: ProductStatus,
stock_status: ProductStockStatus
): Promise<Boolean> {
const res = await this.updateData(`/wc/v3/products/${productId}`, site, {
status,
manage_stock: false, // 为true的时候用quantity控制库存为false时直接用stock_status控制
stock_status,
});
return res;
}
/**
* 更新 WooCommerce 产品变体
* @param productId 产品 ID
* @param variationId 变体 ID
* @param data 更新的数据
*/
async updateVariation(
site: any,
productId: string,
variationId: string,
data: UpdateVariationDTO
): Promise<Boolean> {
const { regular_price, sale_price, ...params } = data;
return await this.updateData(
`/wc/v3/products/${productId}/variations/${variationId}`,
site,
{
...params,
regular_price: regular_price ? regular_price.toString() : null,
sale_price: sale_price ? sale_price.toString() : null,
}
);
}
/**
* 更新 Order
*/
async updateOrder(
site: any,
orderId: string,
data: Record<string, any>
): Promise<Boolean> {
return await this.updateData(`/wc/v3/orders/${orderId}`, site, data);
}
async createShipment(
site: any,
orderId: string,
data: Record<string, any>
) {
const apiUrl = site.apiUrl;
const { consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
const config: AxiosRequestConfig = {
method: 'POST',
// 构建 URL规避多/或少/问题
url: this.buildURL(
apiUrl,
'/wp-json',
'wc-ast/v3/orders',
orderId,
'shipment-trackings'
),
headers: {
Authorization: `Basic ${auth}`,
},
data,
};
return await axios.request(config);
}
async deleteShipment(
site: any,
orderId: string,
trackingId: string,
): Promise<Boolean> {
const apiUrl = site.apiUrl;
const { consumerKey, consumerSecret } = site;
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
'base64'
);
console.log('del', orderId, trackingId);
// 删除接口: DELETE /wp-json/wc-shipment-tracking/v3/orders/<order_id>/shipment-trackings/<tracking_id>
const config: AxiosRequestConfig = {
method: 'DELETE',
// 构建 URL规避多/或少/问题
url: this.buildURL(
apiUrl,
'/wp-json',
'wc-ast/v3/orders',
orderId,
'shipment-trackings',
trackingId
),
headers: {
Authorization: `Basic ${auth}`,
},
};
return await axios.request(config);
}
}