feat(p2): identity layer with IAM service and Teacher BFF

P2 阶段交付物:
- services/iam: 完整身份认证服务(users/roles/permissions/refresh_tokens 6 表 schema)
  - register/login/refresh/getUserInfo 4 个核心 API
  - bcrypt 密码哈希 + JWT 双 Token(access + refresh)
  - 复用 classes 黄金模板(errors/observability/middleware 三件套)
- services/teacher-bff: 教师聚合 BFF
  - Promise.allSettled 并行聚合 IAM + classes 数据
  - /teacher/dashboard 单一聚合端点
- packages/shared-proto/proto/iam.proto: IamService 契约(Register/Login/RefreshToken/GetUserInfo)
- api-gateway: 新增 IamServiceURL/TeacherBffURL 配置 + /iam/* + /teacher/* 路由
This commit is contained in:
SpecialX
2026-07-08 01:37:29 +08:00
parent 2ba4250165
commit 524204d30a
35 changed files with 1108 additions and 1 deletions

View File

@@ -0,0 +1,19 @@
import { Controller, Get, Req } from '@nestjs/common';
import type { Request } from 'express';
import { TeacherService } from './teacher.service.js';
interface AuthenticatedRequest extends Request {
userId?: string;
}
@Controller('teacher')
export class TeacherController {
constructor(private readonly service: TeacherService) {}
@Get('dashboard')
async dashboard(@Req() req: Request) {
const authReq = req as AuthenticatedRequest;
const data = await this.service.getDashboard(authReq.userId as string);
return { success: true, data };
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { TeacherController } from './teacher.controller.js';
import { TeacherService } from './teacher.service.js';
@Module({
controllers: [TeacherController],
providers: [TeacherService],
})
export class TeacherModule {}

View File

@@ -0,0 +1,22 @@
import { Injectable } from '@nestjs/common';
import { env } from '../config/env.js';
@Injectable()
export class TeacherService {
// 聚合 IAM + classes 服务的数据
async getDashboard(userId: string): Promise<unknown> {
const [iamRes, classesRes] = await Promise.allSettled([
fetch(`${env.IamServiceUrl}/iam/me`, {
headers: { 'x-user-id': userId },
}),
fetch(`${env.ClassesServiceUrl}/classes`, {
headers: { 'x-user-id': userId },
}),
]);
return {
user: iamRes.status === 'fulfilled' ? await iamRes.value.json() : null,
classes: classesRes.status === 'fulfilled' ? await classesRes.value.json() : null,
};
}
}