Files
Edu/services/parent-bff/src/graphql/resolvers/dashboard.resolver.ts
SpecialX 2229309a1e feat: initialize parent-bff service with full core features
add complete parent-bff implementation including:
- GraphQL endpoint with depth/cost validation
- ChildGuard越权校验 with redis cache and singleflight
- parallel orchestration with partial failure fallback
- three-level cache fallback strategy (Redis + LRU + downstream)
- Kafka consumer for cache invalidation and notification push
- opossum circuit breaker for downstream services
- Prometheus metrics and SLO alerts
- Helm chart for k8s deployment with multi-environment configs
- Grafana dashboard for observability
- complete unit and integration tests
2026-07-10 18:49:06 +08:00

97 lines
3.1 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 type { GraphqlContext } from "../context.js";
import type { DashboardDataType } from "../types.js";
import type { ResolverDeps } from "./index.js";
import { mapChild, mapParent, mapViewport } from "../../aggregation/response-mapper.js";
import { orchestrate } from "../../aggregation/orchestrator.js";
import { withCacheFallback } from "../../aggregation/fallback-strategy.js";
import { CacheKeys } from "../../shared/cache/cache-key.builder.js";
import { env } from "../../config/env.js";
import { logger } from "../../shared/observability/logger.js";
import type {
ChildDto,
UserInfoDto,
ViewportDto,
} from "../../clients/dtos.js";
/**
* Dashboard Query Resolver对齐 02-architecture-design.md §1.2 + workline P4.6)。
*
* P4.6 增强:
* - Redis 缓存15s TTL对齐 env.DASHBOARD_CACHE_TTL_SECONDS
* - Redis 不可用时降级为内存 LRU
* - 使用 Orchestrator 抽象并行编排 + partial 降级标记
*
* P5 增强:
* - 聚合 msg.listNotifications(unread=true) 获取 unreadNotifications 计数
*
* 聚合 4 个并行调用:
* - iam.getUserInfo → parent
* - iam.getChildrenByParent → children
* - iam.getViewports → viewports
* - msg.listNotifications(unread=true) → unreadNotifications
*
* degraded=true 当任一下游调用失败。
*/
export function buildDashboardResolver(deps: ResolverDeps) {
return async (
_parent: unknown,
_args: unknown,
ctx: GraphqlContext,
): Promise<DashboardDataType> => {
const parentId = ctx.session.parentId;
const cacheKey = CacheKeys.dashboard(parentId);
const result = await withCacheFallback(
cacheKey,
() => fetchDashboard(deps, parentId),
env.DASHBOARD_CACHE_TTL_SECONDS,
"dashboard",
);
if (result.data) {
if (result.fromCache) {
logger.debug({ parentId }, "Dashboard cache hit");
}
return result.data;
}
// 所有降级都失败,返回最小可用数据
logger.warn({ parentId }, "Dashboard all fallbacks exhausted");
return {
parent: null,
children: [],
viewports: [],
unreadNotifications: 0,
degraded: true,
};
};
}
/**
* 实际聚合下游数据Orchestrator 并行 + 降级标记)。
*/
async function fetchDashboard(
deps: ResolverDeps,
parentId: string,
): Promise<DashboardDataType> {
const orchResult = await orchestrate({
parent: deps.iamClient.getUserInfo(parentId),
children: deps.iamClient.getChildrenByParent(parentId),
viewports: deps.iamClient.getViewports(parentId),
unread: deps.msgClient.listNotifications(parentId, true),
});
const parent = orchResult.results.parent as UserInfoDto | null;
const children = orchResult.results.children as ChildDto[] | null;
const viewports = orchResult.results.viewports as ViewportDto[] | null;
const unreadList = orchResult.results.unread as unknown[] | null;
return {
parent: parent ? mapParent(parent) : null,
children: children ? children.map(mapChild) : [],
viewports: viewports ? viewports.map(mapViewport) : [],
unreadNotifications: unreadList ? unreadList.length : 0,
degraded: orchResult.partial,
};
}