All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m7s
- 将meilisearch搜索服务移至Server端
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
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<T>(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<T>;
|
|
});
|
|
|
|
// 并行安全执行
|
|
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<SearchSection<T>> =>
|
|
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;
|
|
};
|