Files
Edu/services/msg/src/shared/push/push-gateway.client.ts
SpecialX 7b7abbb309 feat(msg): 完整实现 msg 消息服务
包含 channels/preferences/templates/grpc/kafka/outbox/push/redis 等完整实现
2026-07-10 19:09:52 +08:00

100 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { env } from "../../config/env.js";
import { logger } from "../observability/logger.js";
/**
* Push Gateway HTTP 客户端。
*
* 仲裁依据:
* - M4HTTP 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;
}
}