# teacher-bff GraphQL Schema (SDL-first, president §2.17) # 版本:v1(P2 第一版,coord 仲裁) # 负责人:ai03 # 关联:contracts/teacher-bff_contract.md §1.3、workline §3.1 # # 设计原则(02-architecture-design.md §5.1): # 1. 按教学场景域组织(不按角色分),教导主任/教研组长复用同一 schema # 2. Query 为主,Mutation 谨慎(BFF 偏读多写少) # 3. N+1 防御:所有 list 字段经 DataLoader(P4 全量接入) # 4. 复杂度限制:depth ≤ 7,query cost ≤ 1000 # 5. admin 命名空间预留(president §5.1:P2 预留 schema,P6 实现 Resolver) # # 错误响应格式:GraphQL errors 数组 + extensions.code = BFF_TEACHER_* + extensions.traceId # 降级模式 B(president §2.6):success=true + error=null + data 内 degraded 字段 # # 阶段标注: # P2 = 当前实现(iam gRPC,未启用字段返 null + extensions.warning = "field_unavailable_in_p2") # P3+ = 跨阶段扩展例外(president §2.3),Resolver 内逐步补 gRPC 调用 scalar DateTime # ============ Types ============ type User { id: ID! email: String! name: String! roles: [String!]! permissions: [String!]! dataScope: DataScope! } enum DataScope { SELF CLASS GRADE SCHOOL DISTRICT ALL } type ViewportItem { key: String! label: String! route: String! icon: String sortOrder: String! requiredPermission: String } type Class { id: ID! name: String! gradeId: String! # 延迟加载:班级下的考试(P3+ core-edu gRPC) exams: [Exam!]! # 延迟加载:班级下的作业(P3+ core-edu gRPC) homework: [Homework!]! } type Exam { id: ID! title: String! classId: ID! status: ExamStatus! publishedAt: DateTime # 延迟加载:考试下的成绩(P3+ core-edu gRPC) grades: [Grade!]! } enum ExamStatus { DRAFT PUBLISHED IN_PROGRESS GRADING SCORED ARCHIVED } type Homework { id: ID! title: String! classId: ID! dueDate: DateTime! submissions: [Submission!]! } type Submission { id: ID! studentId: ID! submittedAt: DateTime! status: SubmissionStatus! } enum SubmissionStatus { NOT_SUBMITTED SUBMITTED GRADED } type Grade { id: ID! examId: ID! studentId: ID! score: Float! rank: Int submittedAt: DateTime! } type DashboardStats { totalExams: Int pendingGrading: Int todayHomework: Int } # Dashboard 聚合数据(P2: iam gRPC 用户基础信息,stats/classes 为 null + warning) type DashboardData { user: User classes: [Class!]! viewports: [ViewportItem!]! stats: DashboardStats } # ============ Query ============ # P2 核心 5 Query(workline §3.2):dashboard / viewports / me / classes / class type Query { # 仪表盘聚合(P2: iam gRPC;P3+: + core-edu + data-ana) # @dataScope: OWN dashboard: DashboardData! # 视口配置(L1 导航,iam.GetViewports) viewports: [ViewportItem!]! # 当前用户信息(iam.GetUserInfo + GetEffectivePermissions) me: User! # 教师班级列表(P2: iam 数据;P3+: core-edu.GetClassesByTeacher) # @dataScope: OWN classes: [Class!]! # 单个班级详情(P3+: core-edu) # @permission: CLASS_READ # @dataScope: OWN class(id: ID!): Class # ===== P3+ 跨阶段扩展(core-edu gRPC,按 §2.3 跨阶段扩展例外新增)===== # 班级下的考试列表(P3: core-edu.ExamService.ListExamsByClass) # @permission: EXAM_READ # @dataScope: OWN exams(classId: ID!): [Exam!]! # 班级下的作业列表(P3: core-edu.HomeworkService.ListHomeworkByClass) # @permission: HOMEWORK_READ # @dataScope: OWN homework(classId: ID!): [Homework!]! # 考试下的成绩列表(P3: core-edu.GradeService.ListGradesByExam) # @permission: GRADE_READ # @dataScope: OWN grades(examId: ID!): [Grade!]! # ===== P4+ 跨阶段扩展(content + data-ana gRPC)===== # 知识图谱学习路径(P4: content.KnowledgeGraphService.GetLearningPath) knowledgePath(knowledgePointId: ID!): KnowledgePath! # 班级成绩分析(P4: data-ana.AnalyticsService.GetClassPerformance) # @permission: ANALYTICS_TEACHER_DASHBOARD classPerformance(classId: ID!): ClassPerformance! # 学生薄弱点(P4: data-ana.AnalyticsService.GetStudentWeakness) studentWeakness(studentId: ID!): StudentWeakness! # 学习趋势(P4: data-ana.AnalyticsService.GetLearningTrend) learningTrend(studentId: ID!, dateRange: DateRangeInput): LearningTrend! # ===== P5+ 跨阶段扩展(ai + msg gRPC)===== # 教师通知列表(P5: msg.NotificationService.ListNotifications) notifications(unreadOnly: Boolean): [Notification!]! # ===== admin 命名空间(president §5.1:P2 预留 schema,P6 实现 Resolver)===== # admin-portal 复用 teacher-bff GraphQL endpoint,admin 操作低 QPS 无需独立 BFF admin: AdminQuery! } input DateRangeInput { start: DateTime! end: DateTime! } # ============ Mutation ============ type Mutation { # ===== P3+ 跨阶段扩展(core-edu gRPC 透传)===== # 创建考试(P3: core-edu.ExamService.CreateExam) # @permission: EXAM_CREATE createExam(input: CreateExamInput!): Exam! # 布置作业(P3: core-edu.HomeworkService.AssignHomework) # @permission: HOMEWORK_ASSIGN assignHomework(input: AssignHomeworkInput!): Homework! # 录入成绩(P3: core-edu.GradeService.RecordGrade) # @permission: GRADE_RECORD recordGrade(input: RecordGradeInput!): Grade! # ===== P5+ 跨阶段扩展(ai + msg gRPC)===== # AI 出题(P5: ai.AiService.GenerateQuestion) generateQuestion(input: GenerateQuestionInput!): GeneratedQuestion! # 标记通知已读(P5: msg.NotificationService.MarkAsRead) markNotificationAsRead(id: ID!): Notification! # ===== admin 命名空间 Mutation(president §5.1:P2 预留,P6 实现)===== admin: AdminMutation! } input CreateExamInput { classId: ID! title: String! subject: String! scheduledAt: DateTime! } input AssignHomeworkInput { classId: ID! title: String! dueDate: DateTime! } input RecordGradeInput { examId: ID! studentId: ID! score: Float! } input GenerateQuestionInput { subject: String! gradeLevel: String! difficulty: String! knowledgePointIds: [ID!]! } # ============ P4+ 类型(content + data-ana)============ type KnowledgePath { knowledgePointId: ID! name: String! prerequisites: [KnowledgePoint!]! } type KnowledgePoint { id: ID! name: String! subject: String! } type ClassPerformance { classId: ID! averageScore: Float! passRate: Float! weaknessTopics: [String!]! } type StudentWeakness { studentId: ID! weakTopics: [String!]! recommendedExercises: [String!]! } type LearningTrend { studentId: ID! trend: [TrendPoint!]! } type TrendPoint { date: DateTime! score: Float! } # ============ P5+ 类型(ai + msg)============ type Notification { id: ID! type: NotificationType! title: String! content: String! read: Boolean! createdAt: DateTime! } enum NotificationType { SYSTEM EXAM HOMEWORK GRADE ATTENDANCE } type GeneratedQuestion { id: ID! subject: String! difficulty: String! content: String! answer: String! } # ============ admin 命名空间(president §5.1:P2 预留 schema,P6 实现 Resolver)============ # P2: 类型声明占位,Resolver 返 null(不阻塞 admin-portal schema 内省) # P6: ai03 实现 admin Resolver 真实数据(用户/角色/学校/组织/审计日志管理) type AdminQuery { # 用户管理 users(page: Int = 1, pageSize: Int = 20): AdminUserPage user(id: ID!): AdminUser # 角色权限管理 roles: [AdminRole!]! role(id: ID!): AdminRole # 学校设置 school: AdminSchool # 组织管理 organizations: [AdminOrganization!]! # 审计日志查询 auditLogs(page: Int = 1, pageSize: Int = 20): AuditLogPage } type AdminMutation { createUser(input: AdminCreateUserInput!): AdminUser! updateUser(id: ID!, input: AdminUpdateUserInput!): AdminUser! deleteUser(id: ID!): Boolean! createRole(input: AdminCreateRoleInput!): AdminRole! updateRole(id: ID!, input: AdminUpdateRoleInput!): AdminRole! deleteRole(id: ID!): Boolean! updateSchool(input: AdminUpdateSchoolInput!): AdminSchool! createOrganization(input: AdminCreateOrganizationInput!): AdminOrganization! updateOrganization(id: ID!, input: AdminUpdateOrganizationInput!): AdminOrganization! deleteOrganization(id: ID!): Boolean! } type AdminUser { id: ID! email: String! name: String! roles: [String!]! status: String! createdAt: DateTime! lastLoginAt: DateTime } type AdminUserPage { edges: [AdminUser!]! totalCount: Int! page: Int! pageSize: Int! } type AdminRole { id: ID! name: String! description: String permissions: [String!]! userCount: Int! } type AdminSchool { id: ID! name: String! address: String phone: String studentCount: Int! teacherCount: Int! classCount: Int! } type AdminOrganization { id: ID! name: String! type: String! parentId: ID createdAt: DateTime! } type AuditLogPage { edges: [AuditLog!]! totalCount: Int! page: Int! pageSize: Int! } type AuditLog { id: ID! actorUserId: ID! action: String! resourceType: String! resourceId: ID occurredAt: DateTime! ip: String } input AdminCreateUserInput { email: String! name: String! roleIds: [ID!]! } input AdminUpdateUserInput { name: String roleIds: [ID!] status: String } input AdminCreateRoleInput { name: String! description: String permissions: [String!]! } input AdminUpdateRoleInput { name: String description: String permissions: [String!] } input AdminUpdateSchoolInput { name: String address: String phone: String } input AdminCreateOrganizationInput { name: String! type: String! parentId: ID } input AdminUpdateOrganizationInput { name: String type: String parentId: ID }