- GraphQLModule: Apollo Federation 2 subgraph at /graphql - UserResolver/RoleResolver: @key with @ResolveReference using DataLoader (ADR-035) - DataScopeResolver: ScopeToken for visible class/student IDs (ADR-041) - RouterAuthGuard: validate Router-Authorization header (ADR-036) - DataLoaderService: REQUEST-scoped userLoader + roleLoader - IamRepository.batchFindRoles: batch query for DataLoader - IamService.getVisibleClassIds/getVisibleStudentIds: dataScope calculation - app.module.ts: register PermissionGuard + RouterAuthGuard as APP_GUARD
70 lines
1.5 KiB
TypeScript
70 lines
1.5 KiB
TypeScript
/**
|
||
* iam User Resolver(v2.1 M1)
|
||
*
|
||
* Apollo Federation 子图:User Entity
|
||
* - @key(fields: "userId") 支持跨子图引用
|
||
* - @ResolveReference 使用 DataLoader 批量加载(ADR-035)
|
||
* - Query 入口供 Apollo Router 直接查询
|
||
*/
|
||
import {
|
||
Resolver,
|
||
Query,
|
||
Args,
|
||
ID,
|
||
ResolveReference,
|
||
ObjectType,
|
||
Field,
|
||
Directive,
|
||
} from "@nestjs/graphql";
|
||
import { DataLoaderService, type UserEntity } from "../dataloader.service.js";
|
||
|
||
/**
|
||
* User ObjectType(Federation @key)
|
||
* 对应 iam.users 表
|
||
*/
|
||
@ObjectType()
|
||
@Directive(`@key(fields: "userId")`)
|
||
export class User {
|
||
@Field(() => ID)
|
||
userId!: string;
|
||
|
||
@Field()
|
||
email!: string;
|
||
|
||
@Field()
|
||
name!: string;
|
||
|
||
@Field()
|
||
status!: string;
|
||
|
||
@Field()
|
||
dataScope!: string;
|
||
}
|
||
|
||
@Resolver(() => User)
|
||
export class UserResolver {
|
||
constructor(private readonly loader: DataLoaderService) {}
|
||
|
||
/**
|
||
* Federation Reference Resolver
|
||
*
|
||
* 当其他子图通过 @key 引用 User 时,Router 调用此方法解析。
|
||
* 使用 DataLoader 批量加载,消除 N+1 查询(ADR-035)。
|
||
*/
|
||
@ResolveReference()
|
||
async resolveReference(ref: { userId: string }): Promise<UserEntity | null> {
|
||
return this.loader.userLoader.load(ref.userId);
|
||
}
|
||
|
||
/**
|
||
* Query: user(userId) → User
|
||
* 通过 Apollo Router 访问,直连被 RouterAuthGuard 拒绝(ADR-036)
|
||
*/
|
||
@Query(() => User, { nullable: true })
|
||
async user(
|
||
@Args("userId", { type: () => ID }) userId: string,
|
||
): Promise<UserEntity | null> {
|
||
return this.loader.userLoader.load(userId);
|
||
}
|
||
}
|