feat: initialize parent-bff service with full core features

add complete parent-bff implementation including:
- GraphQL endpoint with depth/cost validation
- ChildGuard越权校验 with redis cache and singleflight
- parallel orchestration with partial failure fallback
- three-level cache fallback strategy (Redis + LRU + downstream)
- Kafka consumer for cache invalidation and notification push
- opossum circuit breaker for downstream services
- Prometheus metrics and SLO alerts
- Helm chart for k8s deployment with multi-environment configs
- Grafana dashboard for observability
- complete unit and integration tests
This commit is contained in:
SpecialX
2026-07-10 18:49:06 +08:00
parent a7d8f92227
commit 2229309a1e
88 changed files with 11221 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
# parent-bff GraphQL SchemaSDL 契约,对齐 02-architecture-design.md §4.2
#
# 本文件是 parent-bff GraphQL 端点的契约唯一源single source of truth
# - 前端 parent-portal 引用本文件生成 TypeScript 类型
# - 后端 parent-bff 引用本文件加载为 GraphQLSchema
# - CI 强制检测 breaking change与 main 分支对比)
#
# 仲裁依据:
# - U3GraphQL P2 立即引入parent-bff P4 直接用 GraphQL不走 REST 过渡)
# - C1错误码前缀 BFF_PARENT_
# - U4BFF 豁免 @RequirePermission仅校验 x-user-id + ChildGuard
# - 02 §9 #5depth ≤ 7 / cost ≤ 1000
scalar DateTime
# ============ Types ============
type Parent {
id: ID!
email: String!
name: String!
avatar: String
roles: [String!]!
dataScope: DataScope!
}
enum DataScope {
SELF
CHILDREN
CLASS
GRADE
SCHOOL
DISTRICT
ALL
}
type ViewportItem {
key: String!
label: String!
route: String!
icon: String
sortOrder: String!
requiredPermission: String
}
type Child {
id: ID!
name: String!
grade: String!
class: ClassInfo!
lastGrade: Grade
grades(page: Int = 1, pageSize: Int = 20, subject: String): GradePage!
homework: [Homework!]!
exams: [Exam!]!
analytics: ChildAnalytics!
}
type ClassInfo {
id: ID!
name: String!
gradeId: String!
}
type Grade {
id: ID!
examId: ID!
examTitle: String!
subject: String!
score: Float!
rank: Int
gradedAt: DateTime!
}
type GradePage {
items: [Grade!]!
pagination: Pagination!
}
type Pagination {
page: Int!
pageSize: Int!
total: Int!
}
type Homework {
id: ID!
title: String!
classId: ID!
dueDate: DateTime!
status: HomeworkStatus!
submittedAt: DateTime
}
enum HomeworkStatus {
NOT_SUBMITTED
SUBMITTED
GRADED
OVERDUE
}
type Exam {
id: ID!
title: String!
classId: ID!
status: ExamStatus!
examDate: DateTime!
publishedAt: DateTime
}
enum ExamStatus {
DRAFT
PUBLISHED
IN_PROGRESS
GRADING
SCORED
ARCHIVED
}
type ChildAnalytics {
childId: ID!
weakness: [WeaknessTopic!]!
trend: [TrendPoint!]!
classRank: Int
classAverage: Float
}
type WeaknessTopic {
knowledgePointId: ID!
name: String!
subject: String!
masteryRate: Float!
}
type TrendPoint {
date: DateTime!
score: Float!
subject: String
}
type Notification {
id: ID!
type: NotificationType!
title: String!
content: String!
read: Boolean!
childId: ID
createdAt: DateTime!
}
enum NotificationType {
SYSTEM
EXAM
HOMEWORK
GRADE
ATTENDANCE
ANNOUNCEMENT
}
type NotificationPreferences {
channels: [NotificationChannel!]!
eventTypes: NotificationEventTypes!
}
enum NotificationChannel {
APP
SMS
EMAIL
WECHAT
}
type NotificationEventTypes {
gradeReleased: Boolean!
homeworkGraded: Boolean!
examPublished: Boolean!
attendanceAlert: Boolean!
schoolAnnouncement: Boolean!
}
type DashboardData {
parent: Parent
children: [Child!]!
viewports: [ViewportItem!]!
unreadNotifications: Int!
degraded: Boolean!
}
# ============ Query ============
type Query {
dashboard: DashboardData!
viewports: [ViewportItem!]!
me: Parent!
children: [Child!]!
child(childId: ID!): Child
childGrades(
childId: ID!
page: Int = 1
pageSize: Int = 20
subject: String
): GradePage!
childHomework(childId: ID!): [Homework!]!
childExams(childId: ID!): [Exam!]!
childAnalytics(childId: ID!, dateRange: DateRangeInput): ChildAnalytics!
notifications(
unreadOnly: Boolean
page: Int = 1
pageSize: Int = 20
): [Notification!]!
notificationPreferences: NotificationPreferences!
}
input DateRangeInput {
start: DateTime!
end: DateTime!
}
# ============ Mutation ============
type Mutation {
selectChild(childId: ID!): SelectChildResult!
markNotificationRead(notificationId: ID!): Notification!
updateNotificationPreferences(
input: UpdateNotificationPreferencesInput!
): NotificationPreferences!
}
type SelectChildResult {
childId: ID!
selectedAt: DateTime!
audited: Boolean!
}
input UpdateNotificationPreferencesInput {
channels: [NotificationChannel!]!
eventTypes: NotificationEventTypesInput!
}
input NotificationEventTypesInput {
gradeReleased: Boolean
homeworkGraded: Boolean
examPublished: Boolean
attendanceAlert: Boolean
schoolAnnouncement: Boolean
}