feat(shopyy): 实现全量商品查询功能并优化产品相关逻辑
- 新增ShopyyAllProductQuery类支持全量商品查询参数 - 实现getAllProducts方法支持带条件查询 - 优化getProductBySku方法使用新查询接口 - 公开request方法便于子类调用 - 增加错误日志记录产品查找失败情况 - 修复产品permalink生成逻辑
This commit is contained in:
parent
f797950b4c
commit
3664431931
|
|
@ -20,10 +20,11 @@ import {
|
||||||
FulfillmentDTO,
|
FulfillmentDTO,
|
||||||
CreateReviewDTO,
|
CreateReviewDTO,
|
||||||
CreateVariationDTO,
|
CreateVariationDTO,
|
||||||
UpdateReviewDTO
|
UpdateReviewDTO,
|
||||||
} from '../dto/site-api.dto';
|
} from '../dto/site-api.dto';
|
||||||
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
|
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
|
||||||
import {
|
import {
|
||||||
|
ShopyyAllProductQuery,
|
||||||
ShopyyCustomer,
|
ShopyyCustomer,
|
||||||
ShopyyOrder,
|
ShopyyOrder,
|
||||||
ShopyyOrderQuery,
|
ShopyyOrderQuery,
|
||||||
|
|
@ -686,7 +687,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 产品映射方法 ==========
|
// ========== 产品映射方法 ==========
|
||||||
mapPlatformToUnifiedProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO {
|
mapPlatformToUnifiedProduct(item: ShopyyProduct): UnifiedProductDTO {
|
||||||
// 映射产品状态
|
// 映射产品状态
|
||||||
function mapProductStatus(status: number) {
|
function mapProductStatus(status: number) {
|
||||||
return status === 1 ? 'publish' : 'draft';
|
return status === 1 ? 'publish' : 'draft';
|
||||||
|
|
@ -725,7 +726,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
name: c.title || '',
|
name: c.title || '',
|
||||||
})),
|
})),
|
||||||
variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [],
|
variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [],
|
||||||
permalink: item.permalink,
|
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||||||
date_created:
|
date_created:
|
||||||
typeof item.created_at === 'number'
|
typeof item.created_at === 'number'
|
||||||
? new Date(item.created_at * 1000).toISOString()
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
|
@ -782,7 +783,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
// 添加分类信息
|
// 添加分类信息
|
||||||
if (data.categories && data.categories.length > 0) {
|
if (data.categories && data.categories.length > 0) {
|
||||||
params.collections = data.categories.map((category: any) => ({
|
params.collections = data.categories.map((category: any) => ({
|
||||||
id: category.id,
|
// id: category.id,
|
||||||
title: category.name,
|
title: category.name,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
@ -898,10 +899,30 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
per_page,
|
per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
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[]> {
|
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||||
// Shopyy getAllProducts 暂未实现
|
// 转换搜索参数
|
||||||
throw new Error('Shopyy getAllProducts 暂未实现');
|
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> {
|
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||||
|
|
@ -947,12 +968,13 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
// 通过sku获取产品详情的私有方法
|
// 通过sku获取产品详情的私有方法
|
||||||
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
|
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
|
||||||
// 使用Shopyy API的搜索功能通过sku查询产品
|
// 使用Shopyy API的搜索功能通过sku查询产品
|
||||||
const response = await this.shopyyService.getProducts(this.site, 1, 100);
|
const response = await this.getAllProducts({ where: {sku} });
|
||||||
const product = response.items.find((item: any) => item.sku === sku);
|
console.log('getProductBySku', response)
|
||||||
|
const product = response?.[0]
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error(`未找到sku为${sku}的产品`);
|
throw new Error(`未找到sku为${sku}的产品`);
|
||||||
}
|
}
|
||||||
return this.mapPlatformToUnifiedProduct(product);
|
return product
|
||||||
}
|
}
|
||||||
|
|
||||||
async batchProcessProducts(
|
async batchProcessProducts(
|
||||||
|
|
@ -965,6 +987,10 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapSearchParams(query)
|
return this.mapSearchParams(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mapAllProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery {
|
||||||
|
return this.mapSearchParams(query)
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 评论映射方法 ==========
|
// ========== 评论映射方法 ==========
|
||||||
|
|
||||||
mapUnifiedToPlatformReview(data: Partial<UnifiedReviewDTO>) {
|
mapUnifiedToPlatformReview(data: Partial<UnifiedReviewDTO>) {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,53 @@ export interface ShopyyTag {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
export interface ShopyyProductQuery{
|
export interface ShopyyProductQuery {
|
||||||
page: string;
|
page: string;
|
||||||
limit: 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 {
|
export interface ShopyyProduct {
|
||||||
// 产品主键
|
// 产品主键
|
||||||
|
|
@ -259,7 +302,8 @@ export interface ShopyyOrder {
|
||||||
// 创建时间
|
// 创建时间
|
||||||
created_at?: number;
|
created_at?: number;
|
||||||
// 发货商品表 id
|
// 发货商品表 id
|
||||||
id?: number }>;
|
id?: number
|
||||||
|
}>;
|
||||||
}>;
|
}>;
|
||||||
shipping_zone_plans?: Array<{
|
shipping_zone_plans?: Array<{
|
||||||
shipping_price?: number | string;
|
shipping_price?: number | string;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ export enum OrderFulfillmentStatus {
|
||||||
// 确认发货
|
// 确认发货
|
||||||
CONFIRMED,
|
CONFIRMED,
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
export class UnifiedProductWhere {
|
||||||
|
sku?: string;
|
||||||
|
[prop:string]:any
|
||||||
|
}
|
||||||
export class UnifiedTagDTO {
|
export class UnifiedTagDTO {
|
||||||
// 标签DTO用于承载统一标签数据
|
// 标签DTO用于承载统一标签数据
|
||||||
@ApiProperty({ description: '标签ID' })
|
@ApiProperty({ description: '标签ID' })
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ export class ShopyyService {
|
||||||
* @param params 请求参数
|
* @param params 请求参数
|
||||||
* @returns 响应数据
|
* @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 url = this.buildURL(site.apiUrl, endpoint);
|
||||||
const headers = this.buildHeaders(site);
|
const headers = this.buildHeaders(site);
|
||||||
|
|
||||||
|
|
@ -206,13 +206,13 @@ export class ShopyyService {
|
||||||
* @param pageSize 每页数量
|
* @param pageSize 每页数量
|
||||||
* @returns 分页产品列表
|
* @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
|
// ShopYY API: GET /products
|
||||||
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
|
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
|
||||||
const response = await this.request(site, 'products', 'GET', null, {
|
const response = await this.request(site, 'products', 'GET', null, {
|
||||||
page,
|
page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
fields: 'id,name,sku,handle,status,type,stock_status,stock_quantity,images,regular_price,sale_price,tags,variations'
|
...where
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
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 { ShopyyAdapter } from '../adapter/shopyy.adapter';
|
||||||
import { WooCommerceAdapter } from '../adapter/woocommerce.adapter';
|
import { WooCommerceAdapter } from '../adapter/woocommerce.adapter';
|
||||||
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
||||||
|
|
@ -22,6 +22,9 @@ export class SiteApiService {
|
||||||
@Inject()
|
@Inject()
|
||||||
productService: ProductService;
|
productService: ProductService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
async getAdapter(siteId: number): Promise<ISiteAdapter> {
|
async getAdapter(siteId: number): Promise<ISiteAdapter> {
|
||||||
const site = await this.siteService.get(siteId, true);
|
const site = await this.siteService.get(siteId, true);
|
||||||
if (!site) {
|
if (!site) {
|
||||||
|
|
@ -114,7 +117,14 @@ export class SiteApiService {
|
||||||
throw new Error('产品SKU不能为空');
|
throw new Error('产品SKU不能为空');
|
||||||
}
|
}
|
||||||
// 尝试搜索具有相同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) {
|
if (existingProduct) {
|
||||||
// 找到现有产品,更新它
|
// 找到现有产品,更新它
|
||||||
return await adapter.updateProduct({ id: existingProduct.id }, product);
|
return await adapter.updateProduct({ id: existingProduct.id }, product);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue