import { MeiliSearch } from 'meilisearch'; import type { SearchParams } from 'meilisearch'; export class MeiliService { private client: MeiliSearch; private indexes: string[]; constructor() { const runtimeConfig = useRuntimeConfig(); const host = runtimeConfig.public?.meili?.host; const apiKey = runtimeConfig.public?.meili?.searchKey; if (!host) throw new Error('Meilisearch host not configured'); if (!apiKey) throw new Error('Meilisearch server key missing'); this.client = new MeiliSearch({ host, apiKey }); this.indexes = runtimeConfig.public.meili.indexes || []; } /** 构建索引名: products_zh-CN */ private buildIndexNames(locale?: string) { if (!locale) return this.indexes; return this.indexes.map((i) => `${i.trim()}_${locale}`); } /** Server 封装的搜索方法 */ async search< K extends keyof MeiliIndexMap = keyof MeiliIndexMap, T extends MeiliIndexMap[K] = MeiliIndexMap[K], >(query: string, params: SearchParams = {}, locale?: string) { const trimmedQuery = query.trim(); if (!trimmedQuery) return []; const activeIndexes = this.buildIndexNames(locale); if (!activeIndexes.length) { logger.warn('No Meilisearch indexes configured.'); return []; } const requests = activeIndexes.map(async (indexUID) => { const response = await this.client .index(indexUID) .search(trimmedQuery, { limit: params.limit ?? 10, ...params, }); return { indexUid: indexUID, rawIndex: indexUID.replace(`_${locale}`, ''), hits: response.hits, estimatedTotalHits: response.estimatedTotalHits ?? response.hits.length, processingTimeMs: response.processingTimeMs ?? 0, } as SearchSection; }); // 并行安全执行 const results = await Promise.allSettled(requests); results .filter( (result): result is PromiseRejectedResult => result.status === 'rejected' ) .forEach((result) => { logger.error('Meilisearch query failed', result.reason); }); const fulfilled = results.filter( (r): r is PromiseFulfilledResult> => r.status === 'fulfilled' ); return fulfilled.map((r) => r.value).filter((s) => s.hits.length > 0); } } /** 单例 */ let instance: MeiliService | null = null; export const getMeiliService = () => { if (!instance) instance = new MeiliService(); return instance; };