chore(teacher-bff): merge teacher-bff full implementation into main

This commit is contained in:
SpecialX
2026-07-10 19:18:16 +08:00
37 changed files with 2588 additions and 467 deletions

View File

@@ -0,0 +1,452 @@
# 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
}