feat(msg): 修复通知服务并添加ES降级与Push Gateway推送

database.ts 导出db常量替代getDb()函数

env.ts JWT_SECRET/ES_URL改optional加DEV_MODE/PUSH_GATEWAY_URL

elasticsearch.ts ES降级: ES_URL未设置时esClient=null

notifications.service.ts 加createBatch+分页查询+Push Gateway推送调用

新建msg-init.sql创建2张表
This commit is contained in:
SpecialX
2026-07-09 09:08:57 +08:00
parent 421edd8a41
commit 416e1bc0b2
14 changed files with 498 additions and 207 deletions

View File

@@ -1,13 +1,87 @@
import { Client } from '@elastic/elasticsearch';
import { env } from './env.js';
import { Client } from "@elastic/elasticsearch";
import { env } from "./env.js";
export const esClient = new Client({ node: env.ES_URL });
/**
* Elasticsearch 客户端。
*
* 降级模式:当 ES_URL 未设置或连接失败时esClient 为 null
* 所有 ES 操作通过 safeIndex / safeSearch 自动跳过,服务仍可启动。
*/
export const esClient: Client | null = env.ES_URL
? new Client({ node: env.ES_URL })
: null;
/**
* 探活 ES 连接。失败仅记录日志,不抛错(降级模式)。
*/
export async function checkEsConnection(): Promise<void> {
if (!esClient) {
console.log("Elasticsearch disabled (ES_URL not set)");
return;
}
try {
await esClient.ping();
console.log('Elasticsearch connected');
console.log("Elasticsearch connected");
} catch (err) {
console.error('Elasticsearch connection failed:', err);
console.error("Elasticsearch connection failed:", err);
}
}
/**
* 关闭 ES 客户端连接。
*/
export async function closeEs(): Promise<void> {
if (esClient) {
await esClient.close();
}
}
export interface IndexParams {
index: string;
id: string;
document: Record<string, unknown>;
}
export interface SearchHit {
_id: string;
_source: Record<string, unknown>;
}
export interface SearchResult {
hits: SearchHit[];
}
/**
* 安全索引文档。client 为 null 或失败时跳过(降级模式),返回是否成功。
*/
export async function safeIndex(params: IndexParams): Promise<boolean> {
if (!esClient) return false;
try {
await esClient.index(params);
return true;
} catch (err) {
console.error("Elasticsearch index failed:", err);
return false;
}
}
/**
* 安全搜索。client 为 null 或失败时返回空数组(降级模式)。
*/
export async function safeSearch(
index: string,
query: Record<string, unknown>,
): Promise<SearchResult> {
if (!esClient) return { hits: [] };
try {
const result = await esClient.search({ index, query });
const hits = (result.hits.hits as unknown[]).map((raw) => {
const hit = raw as { _id: string; _source: Record<string, unknown> };
return { _id: hit._id, _source: hit._source };
});
return { hits };
} catch (err) {
console.error("Elasticsearch search failed:", err);
return { hits: [] };
}
}