feat(msg): 完整实现 msg 消息服务
包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
This commit is contained in:
99
services/msg/src/shared/push/push-gateway.client.ts
Normal file
99
services/msg/src/shared/push/push-gateway.client.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { env } from "../../config/env.js";
|
||||
import { logger } from "../observability/logger.js";
|
||||
|
||||
/**
|
||||
* Push Gateway HTTP 客户端。
|
||||
*
|
||||
* 仲裁依据:
|
||||
* - M4:HTTP POST /internal/push(豁免 gRPC)
|
||||
* - PushGateway 软失败:不可用时返回 false,不阻断主流程
|
||||
* - 鉴权头:X-Internal-Key = PUSH_INTERNAL_TOKEN
|
||||
*
|
||||
* 请求体对齐 push-gateway handler.go PushHandler:
|
||||
* { userId, event, data }
|
||||
*/
|
||||
|
||||
export interface PushRequest {
|
||||
userId: string;
|
||||
event: string;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface PushResult {
|
||||
sent: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向 push-gateway 发送实时推送。
|
||||
*
|
||||
* 软失败语义:
|
||||
* - PUSH_GATEWAY_URL 未配置 → 返回 { sent: false },不报错
|
||||
* - 网络错误/非 2xx → 返回 { sent: false, error },logger.warn
|
||||
* - 成功 → 返回 { sent: true }
|
||||
*/
|
||||
export async function sendPush(req: PushRequest): Promise<PushResult> {
|
||||
if (!env.PUSH_GATEWAY_URL) {
|
||||
return { sent: false };
|
||||
}
|
||||
|
||||
const url = `${env.PUSH_GATEWAY_URL}/internal/push`;
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
if (env.PUSH_INTERNAL_TOKEN) {
|
||||
headers["X-Internal-Key"] = env.PUSH_INTERNAL_TOKEN;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
userId: req.userId,
|
||||
event: req.event,
|
||||
data: req.data,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const error = `push-gateway returned ${res.status}`;
|
||||
logger.warn({ status: res.status, userId: req.userId }, error);
|
||||
return { sent: false, error };
|
||||
}
|
||||
return { sent: true };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
logger.warn({ err, userId: req.userId }, "Push gateway unavailable");
|
||||
return { sent: false, error };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量推送(逐条发送,push-gateway 无批量 API)。
|
||||
* 任一失败不影响其他,返回每条结果。
|
||||
*/
|
||||
export async function sendPushBatch(
|
||||
requests: PushRequest[],
|
||||
): Promise<PushResult[]> {
|
||||
const results: PushResult[] = [];
|
||||
for (const req of requests) {
|
||||
results.push(await sendPush(req));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查:探测 push-gateway /readyz。
|
||||
* 软失败:不可用返回 false,不阻断 /readyz(仲裁 M2)。
|
||||
*/
|
||||
export async function checkPushGateway(): Promise<boolean> {
|
||||
if (!env.PUSH_GATEWAY_URL) return false;
|
||||
try {
|
||||
const res = await fetch(`${env.PUSH_GATEWAY_URL}/readyz`, {
|
||||
signal: AbortSignal.timeout(2000),
|
||||
});
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user