Compare commits

...

2 Commits

Author SHA1 Message Date
f96207c129 prices optimised 2025-07-18 18:12:06 +03:00
7fc55ab9c3 catalog prices fix 2025-07-17 21:22:20 +03:00
3 changed files with 103 additions and 4 deletions

View File

@ -3018,7 +3018,9 @@ export const resolvers = {
lang, lang,
limit, limit,
page, page,
q q,
params,
hasParams: !!params
}) })
// Преобразуем строку params в объект если передан // Преобразуем строку params в объект если передан
@ -3026,9 +3028,12 @@ export const resolvers = {
if (params) { if (params) {
try { try {
parsedParams = JSON.parse(params); parsedParams = JSON.parse(params);
console.log('📝 Разобранные параметры фильтрации:', parsedParams);
} catch (error) { } catch (error) {
console.warn('⚠️ Не удалось разобрать параметры фильтрации:', params); console.warn('⚠️ Не удалось разобрать параметры фильтрации:', params);
} }
} else {
console.log('📝 Параметры фильтрации отсутствуют');
} }
const entities = await partsIndexService.getCatalogEntities(catalogId, groupId, { const entities = await partsIndexService.getCatalogEntities(catalogId, groupId, {
@ -3064,8 +3069,102 @@ export const resolvers = {
} }
console.log('✅ Получены товары каталога:', entities.list.length) console.log('✅ Получены товары каталога:', entities.list.length)
console.log('🔍 Начинаем серверную фильтрацию по ценам...')
return entities // Глобальный кэш для результатов проверки цен (персистентный между запросами)
if (!global.priceCache) {
global.priceCache = new Map<string, { hasPrice: boolean, timestamp: number }>()
}
const priceCache = global.priceCache as Map<string, { hasPrice: boolean, timestamp: number }>
const CACHE_TTL = 5 * 60 * 1000 // 5 минут
const getCachedPriceResult = (code: string, brand: string): boolean | null => {
const key = `${code}_${brand}`
const cached = priceCache.get(key)
if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) {
return cached.hasPrice
}
return null
}
const cachePriceResult = (code: string, brand: string, hasPrice: boolean): void => {
const key = `${code}_${brand}`
priceCache.set(key, { hasPrice, timestamp: Date.now() })
}
// Фильтруем товары на сервере - проверяем наличие цен в AutoEuro
const filteredEntities: any[] = []
const batchSize = 20 // Увеличенный размер батча для скорости
for (let i = 0; i < entities.list.length; i += batchSize) {
const batch = entities.list.slice(i, i + batchSize)
// Проверяем цены для каждого товара в батче параллельно
const priceCheckPromises = batch.map(async (entity) => {
try {
// Сначала проверяем кэш
const cachedResult = getCachedPriceResult(entity.code, entity.brand.name);
if (cachedResult !== null) {
if (cachedResult) {
console.log(`💨 Кэш: товар ${entity.code} (${entity.brand.name}) имеет цену`);
return entity;
} else {
console.log(`💨 Кэш: товар ${entity.code} (${entity.brand.name}) не имеет цены`);
return null;
}
}
const searchResult = await autoEuroService.searchItems({
code: entity.code,
brand: entity.brand.name,
with_crosses: false,
with_offers: true
})
// Проверяем есть ли предложения с валидной ценой
const hasValidPrice: boolean = Boolean(searchResult.success &&
searchResult.data &&
searchResult.data.length > 0 &&
searchResult.data.some(offer =>
offer.price &&
parseFloat(offer.price.toString()) > 0
))
// Кэшируем результат
cachePriceResult(entity.code, entity.brand.name, hasValidPrice);
if (hasValidPrice) {
console.log(`✅ Товар ${entity.code} (${entity.brand.name}) имеет цену`);
return entity;
} else {
console.log(`❌ Товар ${entity.code} (${entity.brand.name}) не имеет цены`);
return null;
}
} catch (error) {
console.error(`❌ Ошибка проверки цены для ${entity.code}:`, error);
return null // Исключаем товары с ошибками
}
})
// Ждем результаты для текущего батча
const batchResults = await Promise.all(priceCheckPromises)
// Добавляем только товары с ценами
filteredEntities.push(...batchResults.filter(entity => entity !== null))
// Убираем задержку между батчами для максимальной скорости
// if (i + batchSize < entities.list.length) {
// await new Promise(resolve => setTimeout(resolve, 50))
// }
}
console.log(`✅ Серверная фильтрация завершена. Товаров с ценами: ${filteredEntities.length} из ${entities.list.length}`)
// Возвращаем отфильтрованный результат
return {
...entities,
list: filteredEntities
}
} catch (error) { } catch (error) {
console.error('❌ Ошибка в GraphQL resolver partsIndexCatalogEntities:', error) console.error('❌ Ошибка в GraphQL resolver partsIndexCatalogEntities:', error)
throw new Error('Не удалось получить товары каталога') throw new Error('Не удалось получить товары каталога')

View File

@ -37,7 +37,7 @@ class PartsAPIService {
private mediaApiKey: string; private mediaApiKey: string;
constructor() { constructor() {
this.baseURL = 'https://api.partsapi.ru'; this.baseURL = process.env.PARTSAPI_URL || 'https://api.partsapi.ru';
// Получаем ключи API из переменных окружения // Получаем ключи API из переменных окружения
this.categoriesApiKey = process.env.PARTSAPI_CATEGORIES_KEY || ''; this.categoriesApiKey = process.env.PARTSAPI_CATEGORIES_KEY || '';

View File

@ -155,7 +155,7 @@ interface CacheEntry<T> {
} }
class PartsIndexService { class PartsIndexService {
private baseURL = 'https://api.parts-index.com/v1'; private baseURL = process.env.PARTSAPI_URL+"/v1" || 'https://api.parts-index.com/v1';
private apiKey = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE'; private apiKey = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE';
// Простой in-memory кэш // Простой in-memory кэш