Files
Edu/services/teacher-bff/src/teacher/teacher.service.ts
SpecialX 99155a5ea1 feat(teacher-bff): 完整实现 teacher-bff GraphQL 聚合层
包含 clients/graphql/middleware、health probes、shared-ts contracts 等
2026-07-10 19:10:07 +08:00

123 lines
4.0 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.
// TeacherService — GraphQL Resolver 的业务逻辑层B1 裁决P2 起 GraphQL
// 通过 IamClient 调下游 iam gRPCB2 裁决:首次实现即 gRPC
// P2: 仅 iam 数据P3+ 扩展 core-edu / content / data-ana / msg / ai
import { Injectable, Inject } from "@nestjs/common";
import { IAM_CLIENT } from "../clients/iam/iam-client.interface.js";
import type { IamClient } from "../clients/iam/iam-client.interface.js";
import type { CallContext } from "../clients/types.js";
import type {
UserInfo,
ViewportItem,
EffectivePermissions,
} from "../clients/iam/iam.types.js";
import { logger } from "../shared/observability/logger.js";
/** GraphQL User 类型(聚合 UserInfo + EffectivePermissions */
export interface GraphQLUser {
id: string;
email: string;
name: string;
roles: string[];
permissions: string[];
dataScope: string;
}
/** GraphQL DashboardData 类型 */
export interface DashboardData {
user: GraphQLUser | null;
classes: unknown[];
viewports: ViewportItem[];
stats: {
totalExams: number;
pendingGrading: number;
todayHomework: number;
} | null;
}
/** GraphQL Class 类型P2 mockP3+ core-edu 真实数据) */
export interface ClassInfo {
id: string;
name: string;
gradeId: string;
}
/** P2 mock 班级数据president §3.5P2 班级列表来自 iam 数据P3+ core-edu */
const MOCK_CLASSES: ClassInfo[] = [
{ id: "class-001", name: "三年级1班", gradeId: "grade-3" },
{ id: "class-002", name: "三年级2班", gradeId: "grade-3" },
{ id: "class-003", name: "三年级3班", gradeId: "grade-3" },
];
@Injectable()
export class TeacherService {
constructor(@Inject(IAM_CLIENT) private readonly iam: IamClient) {}
/** 获取当前用户信息(聚合 iam.GetUserInfo + GetEffectivePermissions */
async getCurrentUser(ctx: CallContext): Promise<GraphQLUser> {
const [userInfo, perms] = await Promise.all([
this.iam.getUserInfo(ctx),
this.iam.getEffectivePermissions(ctx),
]);
return {
id: userInfo.id,
email: userInfo.email,
name: userInfo.name,
roles: userInfo.roles,
permissions: perms.permissions,
dataScope: perms.dataScope,
};
}
/** 获取视口配置iam.GetViewports */
async getViewports(ctx: CallContext): Promise<ViewportItem[]> {
return this.iam.getViewports(ctx);
}
/**
* 获取 Dashboard 聚合数据president §2.8P2 仅调 iam gRPC
* P2: user + viewports 有数据classes 返回 mockstats 返回 null + warning
* P3+: classes 来自 core-edustats 来自 data-ana.GetTeacherDashboard
*/
async getDashboard(ctx: CallContext): Promise<DashboardData> {
const [user, viewports] = await Promise.all([
this.getCurrentUser(ctx),
this.iam.getViewports(ctx),
]);
// P2: classes 返回 mock 数据P3+ 替换为 core-edu.GetClassesByTeacher
// P2: stats 返回 nullP4+ 替换为 data-ana.GetTeacherDashboard
logger.warn(
{ userId: ctx.userId, phase: "P2" },
"Dashboard P2: classes using mock, stats unavailable (field_unavailable_in_p2)",
);
return {
user,
viewports,
classes: MOCK_CLASSES,
stats: null,
};
}
/** 获取教师班级列表P2: mockP3+: core-edu.GetClassesByTeacher */
async getClasses(ctx: CallContext): Promise<ClassInfo[]> {
logger.warn(
{ userId: ctx.userId, phase: "P2" },
"Classes P2: using mock data (core-edu not ready, field_unavailable_in_p2)",
);
return MOCK_CLASSES;
}
/** 获取单个班级详情P2: mockP3+: core-edu */
async getClass(ctx: CallContext, classId: string): Promise<ClassInfo | null> {
logger.warn(
{ userId: ctx.userId, classId, phase: "P2" },
"Class P2: using mock data (core-edu not ready, field_unavailable_in_p2)",
);
return MOCK_CLASSES.find((c) => c.id === classId) ?? null;
}
}
/** 导出 EffectivePermissions 类型供外部使用 */
export type { EffectivePermissions, UserInfo };