Compare commits
3 Commits
0ccb009773
...
94ed190869
Author | SHA1 | Date | |
---|---|---|---|
94ed190869 | |||
f96207c129 | |||
7fc55ab9c3 |
@ -56,6 +56,7 @@ ARG YOOKASSA_SECRET_KEY
|
|||||||
ARG PARTSAPI_CATEGORIES_KEY
|
ARG PARTSAPI_CATEGORIES_KEY
|
||||||
ARG PARTSAPI_ARTICLES_KEY
|
ARG PARTSAPI_ARTICLES_KEY
|
||||||
ARG PARTSAPI_MEDIA_KEY
|
ARG PARTSAPI_MEDIA_KEY
|
||||||
|
ARG PARTSAPI_URL
|
||||||
ARG PARTSINDEX_API_KEY
|
ARG PARTSINDEX_API_KEY
|
||||||
ARG YANDEX_MAPS_API_KEY
|
ARG YANDEX_MAPS_API_KEY
|
||||||
ARG YANDEX_DELIVERY_TOKEN
|
ARG YANDEX_DELIVERY_TOKEN
|
||||||
|
@ -29,6 +29,7 @@ services:
|
|||||||
- PARTSAPI_CATEGORIES_KEY=${PARTSAPI_CATEGORIES_KEY}
|
- PARTSAPI_CATEGORIES_KEY=${PARTSAPI_CATEGORIES_KEY}
|
||||||
- PARTSAPI_ARTICLES_KEY=${PARTSAPI_ARTICLES_KEY}
|
- PARTSAPI_ARTICLES_KEY=${PARTSAPI_ARTICLES_KEY}
|
||||||
- PARTSAPI_MEDIA_KEY=${PARTSAPI_MEDIA_KEY}
|
- PARTSAPI_MEDIA_KEY=${PARTSAPI_MEDIA_KEY}
|
||||||
|
- PARTSAPI_URL=${PARTSAPI_URL}
|
||||||
- PARTSINDEX_API_KEY=${PARTSINDEX_API_KEY}
|
- PARTSINDEX_API_KEY=${PARTSINDEX_API_KEY}
|
||||||
- YANDEX_MAPS_API_KEY=${YANDEX_MAPS_API_KEY}
|
- YANDEX_MAPS_API_KEY=${YANDEX_MAPS_API_KEY}
|
||||||
- YANDEX_DELIVERY_TOKEN=${YANDEX_DELIVERY_TOKEN}
|
- YANDEX_DELIVERY_TOKEN=${YANDEX_DELIVERY_TOKEN}
|
||||||
@ -79,6 +80,7 @@ services:
|
|||||||
- PARTSAPI_CATEGORIES_KEY=${PARTSAPI_CATEGORIES_KEY}
|
- PARTSAPI_CATEGORIES_KEY=${PARTSAPI_CATEGORIES_KEY}
|
||||||
- PARTSAPI_ARTICLES_KEY=${PARTSAPI_ARTICLES_KEY}
|
- PARTSAPI_ARTICLES_KEY=${PARTSAPI_ARTICLES_KEY}
|
||||||
- PARTSAPI_MEDIA_KEY=${PARTSAPI_MEDIA_KEY}
|
- PARTSAPI_MEDIA_KEY=${PARTSAPI_MEDIA_KEY}
|
||||||
|
- PARTSAPI_URL=${PARTSAPI_URL}
|
||||||
|
|
||||||
# PartsIndex API
|
# PartsIndex API
|
||||||
- PARTSINDEX_API_KEY=${PARTSINDEX_API_KEY}
|
- PARTSINDEX_API_KEY=${PARTSINDEX_API_KEY}
|
||||||
|
@ -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('Не удалось получить товары каталога')
|
||||||
|
@ -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 || '';
|
||||||
|
@ -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 кэш
|
||||||
|
@ -69,6 +69,10 @@ PARTSAPI_MEDIA_KEY=230d8c7118a36cc6d36d72681b76982b
|
|||||||
# API ключ для PartsIndex (каталог автотоваров)
|
# API ключ для PartsIndex (каталог автотоваров)
|
||||||
PARTSINDEX_API_KEY=PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE
|
PARTSINDEX_API_KEY=PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE
|
||||||
|
|
||||||
|
# ===== PARTSAPI =====
|
||||||
|
# URL для API Parts-Index
|
||||||
|
PARTSAPI_URL=https://api.parts-index.com
|
||||||
|
|
||||||
# ===== S3 ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ =====
|
# ===== S3 ДОПОЛНИТЕЛЬНЫЕ НАСТРОЙКИ =====
|
||||||
# Альтернативное название бакета (если отличается от AWS_BUCKET_NAME)
|
# Альтернативное название бакета (если отличается от AWS_BUCKET_NAME)
|
||||||
AWS_S3_BUCKET=your_s3_bucket_name
|
AWS_S3_BUCKET=your_s3_bucket_name
|
||||||
|
Reference in New Issue
Block a user