feat(student-bff): 完整实现 student-bff 聚合层

包含 src 全部实现、Dockerfile、shared-ts/bff 包等
This commit is contained in:
SpecialX
2026-07-10 19:10:51 +08:00
parent e5ca4c6c7b
commit f585080e70
55 changed files with 7141 additions and 252 deletions

View File

@@ -0,0 +1,13 @@
/**
* student-bff pino logger.
*
* 仲裁依据: coord-final-decisions §1 G4 (首次实现即 pino 结构化日志)
* coord-final-decisions §2 B8 (复用 shared-ts BFF logger 工厂)
*/
import { createBffLogger, type Logger } from "@edu/shared-ts/bff";
export const logger: Logger = createBffLogger("student-bff", {
level: process.env.LOG_LEVEL ?? "info",
});
export type { Logger };

View File

@@ -0,0 +1,159 @@
/**
* student-bff prom-client metrics.
*
* 仲裁依据: coord-final-decisions §1 G5 (首次实现即 /metrics + 基础业务指标)
* 02-architecture-design.md §6.4 (11 个 student_bff_* 指标)
*
* 指标命名: <service>_<module>_<operation>_<unit> (project_rules §12)
*/
import promClient from "prom-client";
const registry = new promClient.Registry();
registry.setDefaultLabels({ service: "student-bff" });
// 1. 请求总数
registry.registerMetric(
new promClient.Counter({
name: "student_bff_requests_total",
help: "Total number of student-bff HTTP/GraphQL requests",
labelNames: ["operation", "method", "status"],
}),
);
// 2. 请求延迟
registry.registerMetric(
new promClient.Histogram({
name: "student_bff_request_duration_seconds",
help: "Student-bff request duration in seconds",
labelNames: ["operation", "method"],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 3, 5],
}),
);
// 3. 下游调用总数
registry.registerMetric(
new promClient.Counter({
name: "student_bff_downstream_calls_total",
help: "Total downstream gRPC calls",
labelNames: ["service", "method", "status"],
}),
);
// 4. 下游调用延迟
registry.registerMetric(
new promClient.Histogram({
name: "student_bff_downstream_duration_seconds",
help: "Downstream gRPC call duration in seconds",
labelNames: ["service", "method"],
buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 3, 5],
}),
);
// 5. 下游错误数
registry.registerMetric(
new promClient.Counter({
name: "student_bff_downstream_errors_total",
help: "Downstream gRPC call errors",
labelNames: ["service", "method", "error_type"],
}),
);
// 6. 缓存命中
registry.registerMetric(
new promClient.Counter({
name: "student_bff_cache_hits_total",
help: "Cache hit count",
labelNames: ["cache_key_pattern"],
}),
);
// 7. 缓存未命中
registry.registerMetric(
new promClient.Counter({
name: "student_bff_cache_misses_total",
help: "Cache miss count",
labelNames: ["cache_key_pattern"],
}),
);
// 8. 熔断器状态 (P6)
registry.registerMetric(
new promClient.Gauge({
name: "student_bff_circuit_state",
help: "Circuit breaker state (0=closed, 1=open, 2=half-open)",
labelNames: ["service", "state"],
}),
);
// 9. SSE 连接数 (P5)
registry.registerMetric(
new promClient.Gauge({
name: "student_bff_sse_connections",
help: "Active SSE connections",
}),
);
// 10. 事件消费数 (P5)
registry.registerMetric(
new promClient.Counter({
name: "student_bff_event_consumed_total",
help: "Kafka events consumed",
labelNames: ["topic", "event_type"],
}),
);
// 11. 推送数 (P5)
registry.registerMetric(
new promClient.Counter({
name: "student_bff_event_pushed_total",
help: "Push-gateway push count",
labelNames: ["topic", "push_status"],
}),
);
// 自动收集 Node.js 进程级指标
promClient.collectDefaultMetrics({ register: registry });
export { registry as metricsRegistry };
/**
* 下游调用指标辅助器 (供 DownstreamClient 调用).
*/
export function recordDownstreamCall(
service: string,
method: string,
status: "success" | "error",
durationMs: number,
errorType?: string,
): void {
const labels = { service, method, status };
registry
.getSingleMetric("student_bff_downstream_calls_total")
?.inc(labels);
registry
.getSingleMetric("student_bff_downstream_duration_seconds")
?.observe({ service, method }, durationMs / 1000);
if (status === "error" && errorType) {
registry
.getSingleMetric("student_bff_downstream_errors_total")
?.inc({ service, method, error_type: errorType });
}
}
/**
* 缓存命中/未命中指标辅助器.
*/
export function recordCacheAccess(
keyPattern: string,
hit: boolean,
): void {
if (hit) {
registry
.getSingleMetric("student_bff_cache_hits_total")
?.inc({ cache_key_pattern: keyPattern });
} else {
registry
.getSingleMetric("student_bff_cache_misses_total")
?.inc({ cache_key_pattern: keyPattern });
}
}

View File

@@ -0,0 +1,42 @@
/**
* student-bff OpenTelemetry tracer.
*
* 仲裁依据: coord-final-decisions §1 G6 (首次实现即 OTel SDK + OTLP exporter + 完整资源属性 + 全链路 span)
* 02-architecture-design.md §6.5 (serviceName: student-bff)
*/
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { env } from "../../config/env.js";
import { logger } from "./logger.js";
let sdk: NodeSDK | null = null;
export function initTracer(): void {
if (!env.OTEL_EXPORTER_OTLP_ENDPOINT) {
logger.warn("OTEL_EXPORTER_OTLP_ENDPOINT not set, tracer disabled");
return;
}
sdk = new NodeSDK({
serviceName: env.OTEL_SERVICE_NAME,
traceExporter: new OTLPTraceExporter({
url: `${env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
logger.info(
{ endpoint: env.OTEL_EXPORTER_OTLP_ENDPOINT, service: env.OTEL_SERVICE_NAME },
"Tracer initialized with auto-instrumentations",
);
}
export async function shutdownTracer(): Promise<void> {
if (sdk) {
await sdk.shutdown();
sdk = null;
logger.debug("Tracer shutdown complete");
}
}