feat(shopyy): 实现全量商品查询功能并优化产品相关逻辑
- 新增ShopyyAllProductQuery类支持全量商品查询参数 - 实现getAllProducts方法支持带条件查询 - 优化getProductBySku方法使用新查询接口 - 公开request方法便于子类调用 - 增加错误日志记录产品查找失败情况 - 修复产品permalink生成逻辑
This commit is contained in:
parent
bdc2af3514
commit
8bdc438a48
|
|
@ -20,14 +20,11 @@ import {
|
|||
FulfillmentDTO,
|
||||
CreateReviewDTO,
|
||||
CreateVariationDTO,
|
||||
UpdateReviewDTO
|
||||
FulfillmentDTO,
|
||||
CreateReviewDTO,
|
||||
CreateVariationDTO,
|
||||
UpdateReviewDTO
|
||||
UpdateReviewDTO,
|
||||
} from '../dto/site-api.dto';
|
||||
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
|
||||
import {
|
||||
ShopyyAllProductQuery,
|
||||
ShopyyCustomer,
|
||||
ShopyyOrder,
|
||||
ShopyyOrderQuery,
|
||||
|
|
@ -718,7 +715,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
}
|
||||
|
||||
// ========== 产品映射方法 ==========
|
||||
mapPlatformToUnifiedProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO {
|
||||
mapPlatformToUnifiedProduct(item: ShopyyProduct): UnifiedProductDTO {
|
||||
// 映射产品状态
|
||||
function mapProductStatus(status: number) {
|
||||
return status === 1 ? 'publish' : 'draft';
|
||||
|
|
@ -757,7 +754,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
name: c.title || '',
|
||||
})),
|
||||
variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [],
|
||||
permalink: item.permalink,
|
||||
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||||
date_created:
|
||||
typeof item.created_at === 'number'
|
||||
? new Date(item.created_at * 1000).toISOString()
|
||||
|
|
@ -814,7 +811,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
// 添加分类信息
|
||||
if (data.categories && data.categories.length > 0) {
|
||||
params.collections = data.categories.map((category: any) => ({
|
||||
id: category.id,
|
||||
// id: category.id,
|
||||
title: category.name,
|
||||
}));
|
||||
}
|
||||
|
|
@ -941,20 +938,32 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
per_page,
|
||||
};
|
||||
}
|
||||
|
||||
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||
// Shopyy getAllProducts 暂未实现
|
||||
throw new Error('Shopyy getAllProducts 暂未实现');
|
||||
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||
// Shopyy getAllProducts 暂未实现
|
||||
throw new Error('Shopyy getAllProducts 暂未实现');
|
||||
mapAllProductParams(params: UnifiedSearchParamsDTO): Partial<ShopyyAllProductQuery>{
|
||||
const mapped = {
|
||||
...params.where,
|
||||
} as any
|
||||
if(params.per_page){mapped.limit = params.per_page}
|
||||
return mapped
|
||||
}
|
||||
|
||||
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||
// 转换搜索参数
|
||||
const requestParams = this.mapAllProductParams(params);
|
||||
const response = await this.shopyyService.request(
|
||||
this.site,
|
||||
'products',
|
||||
'GET',
|
||||
null,
|
||||
requestParams
|
||||
);
|
||||
if(response.code !==0){
|
||||
throw new Error(response.msg || '获取产品列表失败')
|
||||
}
|
||||
const { data = [] } = response;
|
||||
const finalItems = data.map(this.mapPlatformToUnifiedProduct.bind(this))
|
||||
return finalItems
|
||||
}
|
||||
|
||||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||
// 使用映射方法转换参数
|
||||
const requestParams = this.mapCreateProductParams(data);
|
||||
const res = await this.shopyyService.createProduct(this.site, requestParams);
|
||||
return this.mapPlatformToUnifiedProduct(res);
|
||||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||
// 使用映射方法转换参数
|
||||
const requestParams = this.mapCreateProductParams(data);
|
||||
|
|
@ -998,12 +1007,13 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
// 通过sku获取产品详情的私有方法
|
||||
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
|
||||
// 使用Shopyy API的搜索功能通过sku查询产品
|
||||
const response = await this.shopyyService.getProducts(this.site, 1, 100);
|
||||
const product = response.items.find((item: any) => item.sku === sku);
|
||||
const response = await this.getAllProducts({ where: {sku} });
|
||||
console.log('getProductBySku', response)
|
||||
const product = response?.[0]
|
||||
if (!product) {
|
||||
throw new Error(`未找到sku为${sku}的产品`);
|
||||
}
|
||||
return this.mapPlatformToUnifiedProduct(product);
|
||||
return product
|
||||
}
|
||||
|
||||
async batchProcessProducts(
|
||||
|
|
@ -1016,6 +1026,10 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
return this.mapSearchParams(query)
|
||||
}
|
||||
|
||||
mapAllProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery {
|
||||
return this.mapSearchParams(query)
|
||||
}
|
||||
|
||||
// ========== 评论映射方法 ==========
|
||||
|
||||
mapUnifiedToPlatformReview(data: Partial<UnifiedReviewDTO>) {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,53 @@ export interface ShopyyTag {
|
|||
id?: number;
|
||||
name?: string;
|
||||
}
|
||||
export interface ShopyyProductQuery{
|
||||
export interface ShopyyProductQuery {
|
||||
page: string;
|
||||
limit: string;
|
||||
}
|
||||
/**
|
||||
* Shopyy 全量商品查询参数类
|
||||
* 用于封装获取 Shopyy 商品列表时的各种筛选和分页条件
|
||||
* 参考文档: https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||||
*/
|
||||
export class ShopyyAllProductQuery {
|
||||
/** 分页大小,限制返回的商品数量 */
|
||||
limit?: string;
|
||||
/** 起始ID,用于分页,返回ID大于该值的商品 */
|
||||
since_id?: string;
|
||||
/** 商品ID,精确匹配单个商品 */
|
||||
id?: string;
|
||||
/** 商品标题,支持模糊查询 */
|
||||
title?: string;
|
||||
/** 商品状态,例如:上架、下架、删除等(具体值参考 Shopyy 接口文档) */
|
||||
status?: string;
|
||||
/** 商品SKU编码,库存保有单位,精确或模糊匹配 */
|
||||
sku?: string;
|
||||
/** 商品SPU编码,标准化产品单元,用于归类同款商品 */
|
||||
spu?: string;
|
||||
/** 商品分类ID,筛选指定分类下的商品 */
|
||||
collection_id?: string;
|
||||
/** 变体价格最小值,筛选变体价格大于等于该值的商品 */
|
||||
variant_price_min?: string;
|
||||
/** 变体价格最大值,筛选变体价格小于等于该值的商品 */
|
||||
variant_price_max?: string;
|
||||
/** 变体划线价(原价)最小值,筛选变体划线价大于等于该值的商品 */
|
||||
variant_compare_at_price_min?: string;
|
||||
/** 变体划线价(原价)最大值,筛选变体划线价小于等于该值的商品 */
|
||||
variant_compare_at_price_max?: string;
|
||||
/** 变体重量最小值,筛选变体重量大于等于该值的商品(单位参考接口文档) */
|
||||
variant_weight_min?: string;
|
||||
/** 变体重量最大值,筛选变体重量小于等于该值的商品(单位参考接口文档) */
|
||||
variant_weight_max?: string;
|
||||
/** 商品创建时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
created_at_min?: string;
|
||||
/** 商品创建时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
created_at_max?: string;
|
||||
/** 商品更新时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
updated_at_min?: string;
|
||||
/** 商品更新时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||
updated_at_max?: string;
|
||||
}
|
||||
// 产品类型
|
||||
export interface ShopyyProduct {
|
||||
// 产品主键
|
||||
|
|
@ -259,7 +302,8 @@ export interface ShopyyOrder {
|
|||
// 创建时间
|
||||
created_at?: number;
|
||||
// 发货商品表 id
|
||||
id?: number }>;
|
||||
id?: number
|
||||
}>;
|
||||
}>;
|
||||
shipping_zone_plans?: Array<{
|
||||
shipping_price?: number | string;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ export enum OrderFulfillmentStatus {
|
|||
// 确认发货
|
||||
CONFIRMED,
|
||||
}
|
||||
//
|
||||
export class UnifiedProductWhere {
|
||||
sku?: string;
|
||||
[prop:string]:any
|
||||
}
|
||||
export class UnifiedTagDTO {
|
||||
// 标签DTO用于承载统一标签数据
|
||||
@ApiProperty({ description: '标签ID' })
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export class ShopyyService {
|
|||
* @param params 请求参数
|
||||
* @returns 响应数据
|
||||
*/
|
||||
private async request(site: any, endpoint: string, method: string = 'GET', data: any = null, params: any = null): Promise<any> {
|
||||
async request(site: any, endpoint: string, method: string = 'GET', data: any = null, params: any = null): Promise<any> {
|
||||
const url = this.buildURL(site.apiUrl, endpoint);
|
||||
const headers = this.buildHeaders(site);
|
||||
|
||||
|
|
@ -206,13 +206,13 @@ export class ShopyyService {
|
|||
* @param pageSize 每页数量
|
||||
* @returns 分页产品列表
|
||||
*/
|
||||
async getProducts(site: any, page: number = 1, pageSize: number = 100): Promise<any> {
|
||||
async getProducts(site: any, page: number = 1, pageSize: number = 100, where: Record<string, any> = {}): Promise<any> {
|
||||
// ShopYY API: GET /products
|
||||
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
|
||||
const response = await this.request(site, 'products', 'GET', null, {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
fields: 'id,name,sku,handle,status,type,stock_status,stock_quantity,images,regular_price,sale_price,tags,variations'
|
||||
...where
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Inject, Provide } from '@midwayjs/core';
|
||||
import { ILogger, Inject, Provide } from '@midwayjs/core';
|
||||
import { ShopyyAdapter } from '../adapter/shopyy.adapter';
|
||||
import { WooCommerceAdapter } from '../adapter/woocommerce.adapter';
|
||||
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
||||
|
|
@ -22,6 +22,9 @@ export class SiteApiService {
|
|||
@Inject()
|
||||
productService: ProductService;
|
||||
|
||||
@Inject()
|
||||
logger: ILogger;
|
||||
|
||||
async getAdapter(siteId: number): Promise<ISiteAdapter> {
|
||||
const site = await this.siteService.get(siteId, true);
|
||||
if (!site) {
|
||||
|
|
@ -114,7 +117,14 @@ export class SiteApiService {
|
|||
throw new Error('产品SKU不能为空');
|
||||
}
|
||||
// 尝试搜索具有相同SKU的产品
|
||||
const existingProduct = await adapter.getProduct({ sku: product.sku });
|
||||
let existingProduct
|
||||
try {
|
||||
|
||||
existingProduct = await adapter.getProduct({ sku: product.sku });
|
||||
} catch (error) {
|
||||
this.logger.error(`[Site API] 查找产品失败, siteId: ${siteId}, sku: ${product.sku}, 错误信息: ${error.message}`);
|
||||
existingProduct = null
|
||||
}
|
||||
if (existingProduct) {
|
||||
// 找到现有产品,更新它
|
||||
return await adapter.updateProduct({ id: existingProduct.id }, product);
|
||||
|
|
|
|||
Loading…
Reference in New Issue