fix: 修复Server搜索问题
All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m7s

- 将meilisearch搜索服务移至Server端
This commit is contained in:
2025-11-14 14:57:55 +08:00
parent b2b631ed46
commit 17bb8adee3
6 changed files with 148 additions and 175 deletions

85
server/services/search.ts Normal file
View File

@ -0,0 +1,85 @@
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;
};