import { Config, Provide } from '@midwayjs/core'; import axios, { AxiosRequestConfig } from 'axios'; import { WpSite } from '../interface'; 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'; @Provide() export class WPService { @Config('wpSite') sites: WpSite[]; /** * 构建 URL,自动规范各段的斜杠,避免出现多 / 或少 / 导致请求失败 * 使用示例:this.buildURL(wpApiUrl, '/wp-json', 'wc/v3/products', productId) */ private buildURL(base: string, ...parts: Array): 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/'); } /** * 获取 WordPress 数据 * @param wpApiUrl WordPress REST API 的基础地址 * @param endpoint API 端点路径(例如 wc/v3/products) * @param consumerKey WooCommerce 的消费者密钥 * @param consumerSecret WooCommerce 的消费者密钥 */ geSite(id: string): WpSite { let idx = this.sites.findIndex(item => item.id === id); return this.sites[idx]; } async fetchData( endpoint: string, site: WpSite, param: Record = {} ): Promise { try { const { wpApiUrl, consumerKey, consumerSecret } = site; // 构建 URL,规避多/或少/问题 const url = this.buildURL(wpApiUrl, '/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( endpoint: string, site: WpSite, page: number = 1, perPage: number = 100 ): Promise { const allData: T[] = []; const { wpApiUrl, consumerKey, consumerSecret } = site; const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString( 'base64' ); let hasMore = true; while (hasMore) { const config: AxiosRequestConfig = { method: 'GET', // 构建 URL,规避多/或少/问题 url: this.buildURL(wpApiUrl, '/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: WpSite): Promise { return await this.fetchPagedData('/wc/v3/products', site); } async getVariations(site: WpSite, productId: number): Promise { return await this.fetchPagedData( `/wc/v3/products/${productId}/variations`, site ); } async getVariation( site: WpSite, productId: number, variationId: number ): Promise { return await this.fetchData( `/wc/v3/products/${productId}/variations/${variationId}`, site ); } async getOrder( siteId: string, orderId: string ): Promise> { const site = this.geSite(siteId); return await this.fetchData>( `/wc/v3/orders/${orderId}`, site ); } async getOrders(siteId: string): Promise[]> { const site = this.geSite(siteId); return await this.fetchPagedData>( '/wc/v3/orders', site ); } /** * 获取 WooCommerce Subscriptions * 优先尝试 wc/v1/subscriptions(Subscriptions 插件提供),失败时回退 wc/v3/subscriptions。 * 返回所有分页合并后的订阅数组。 */ async getSubscriptions(siteId: string): Promise[]> { const site = this.geSite(siteId); try { return await this.fetchPagedData>( '/wc/v1/subscriptions', site ); } catch (e) { // fallback return await this.fetchPagedData>( '/wc/v3/subscriptions', site ); } } async getOrderRefund( siteId: string, orderId: string, refundId: number ): Promise> { const site = this.geSite(siteId); return await this.fetchData>( `/wc/v3/orders/${orderId}/refunds/${refundId}`, site ); } async getOrderRefunds( siteId: string, orderId: number ): Promise[]> { const site = this.geSite(siteId); return await this.fetchPagedData>( `/wc/v3/orders/${orderId}/refunds`, site ); } async getOrderNote( siteId: string, orderId: number, noteId: number ): Promise> { const site = this.geSite(siteId); return await this.fetchData>( `/wc/v3/orders/${orderId}/notes/${noteId}`, site ); } async getOrderNotes( siteId: string, orderId: number ): Promise[]> { const site = this.geSite(siteId); return await this.fetchPagedData>( `/wc/v3/orders/${orderId}/notes`, site ); } async updateData( endpoint: string, site: WpSite, data: Record ): Promise { const { wpApiUrl, consumerKey, consumerSecret } = site; const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString( 'base64' ); const config: AxiosRequestConfig = { method: 'PUT', // 构建 URL,规避多/或少/问题 url: this.buildURL(wpApiUrl, '/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: WpSite, productId: string, data: UpdateWpProductDTO ): Promise { 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: WpSite, productId: string, status: ProductStatus, stock_status: ProductStockStatus ): Promise { 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: WpSite, productId: string, variationId: string, data: UpdateVariationDTO ): Promise { 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: WpSite, orderId: string, data: Record ): Promise { return await this.updateData(`/wc/v3/orders/${orderId}`, site, data); } async createShipment( site: WpSite, orderId: string, data: Record ) { const { wpApiUrl, consumerKey, consumerSecret } = site; const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString( 'base64' ); const config: AxiosRequestConfig = { method: 'POST', // 构建 URL,规避多/或少/问题 url: this.buildURL( wpApiUrl, '/wp-json', 'wc-ast/v3/orders', orderId, 'shipment-trackings' ), headers: { Authorization: `Basic ${auth}`, }, data, }; return await axios.request(config); } async deleteShipment( site: WpSite, orderId: string, trackingId: string, ): Promise { const { wpApiUrl, consumerKey, consumerSecret } = site; const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString( 'base64' ); console.log('del', orderId, trackingId); // 删除接口: DELETE /wp-json/wc-shipment-tracking/v3/orders//shipment-trackings/ const config: AxiosRequestConfig = { method: 'DELETE', // 构建 URL,规避多/或少/问题 url: this.buildURL( wpApiUrl, '/wp-json', 'wc-ast/v3/orders', orderId, 'shipment-trackings', trackingId ), headers: { Authorization: `Basic ${auth}`, }, }; return await axios.request(config); } }