feat(api-gateway): retire teacher/student/parent-bff, route GraphQL to apollo-router

v2.1 M9+M10: BFF layer and old portals retired.

- api-gateway: remove TeacherBffURL/StudentBffURL/ParentBffURL config

- api-gateway: add ApolloRouterURL config and /api/graphql route

- api-gateway: /api/admin/graphql now proxies to apollo-router

- api-gateway: health checks now ping apollo-router instead of BFF

- deploy.yml: replace teacher-bff service block with apollo-router

- deploy.yml: add config-service service block (M3 dependency)

- deploy.yml: remove teacher-portal and admin-portal service blocks

- source code in services/{teacher,student,parent}-bff/ and apps/*-portal/ retained for rollback
This commit is contained in:
SpecialX
2026-07-15 08:14:24 +08:00
parent 514e26ebb4
commit 83b6919185
4 changed files with 105 additions and 129 deletions

View File

@@ -79,15 +79,9 @@ func main() {
registerProxy(api, "classes", cfg.CoreEduServiceURL)
// iam 服务路由(身份与访问管理)
registerProxy(api, "iam", cfg.IamServiceURL)
// teacher-bff 路由(教师聚合层 GraphQL
// BFF 在 /graphql 提供服务(@Controller("graphql")),需剥离 /api/v1/teacher 前缀
// 契约 student-portal_contract.md §2.3/api/v1/{bff}/* → {bff}:port/*
registerBffProxy(api, "teacher", cfg.TeacherBffURL)
// student-bff 路由(学生聚合层 GraphQLP3
registerBffProxy(api, "student", cfg.StudentBffURL)
// parent-bff 路由(家长聚合层 GraphQLP4
// parent-bff 在 /v1/graphql 提供服务ARB-022 §24.4 ISSUE-003 方案 A
registerBffProxy(api, "parent", cfg.ParentBffURL)
// v2.1 M9teacher-bff / student-bff / parent-bff 已下线,
// 前端 GraphQL 查询统一通过 apollo-router 聚邦层。
// /api/v1/teacher/* /api/v1/student/* /api/v1/parent/* 路由已移除。
// core-edu 域路由(考试/作业/成绩)—— 下游 controller 在 /v1/{domain}/*,仅需剥离 /api
registerProxy(api, "exams", cfg.CoreEduServiceURL)
registerProxy(api, "homework", cfg.CoreEduServiceURL)
@@ -110,25 +104,43 @@ func main() {
registerProxy(api, "dashboard", cfg.DataAnaServiceURL)
}
// admin 路由组admin-portal 入口(契约 admin-portal_contract.md §2.3
// POST /api/admin/graphql → teacher-bff:3003/graphqladmin 命名空间
// 中间件链:熔断 → JWT 鉴权 → admin 角色强制 → 指标 → 反向代理
// v2.1 M9GraphQL 联邦入口(替代旧 BFF 聚合层
// /api/graphql → apollo-router:3000/graphql统一前端 GraphQL 入口
// /api/admin/graphql → apollo-router:3000/graphqladmin 命名空间)
// 中间件链:熔断 → JWT 鉴权 → 指标 → 反向代理admin 路由额外强制 admin 角色)
graphqlGroup := r.Group("/api/graphql")
graphqlGroup.Use(middleware.CircuitBreaker("apollo-router"))
graphqlGroup.Use(middleware.AuthMiddleware(cfg, fetcher))
graphqlGroup.Use(observability.Metrics())
{
// 路径重写:/api/graphql → /graphqlapollo-router supergraph.path
graphqlProxy, err := proxy.NewProxyRewrite(cfg.ApolloRouterURL, func(p string) string {
return strings.TrimPrefix(p, "/api/graphql")
})
if err != nil {
slog.Error("failed to create apollo-router graphql proxy", "target", cfg.ApolloRouterURL, "error", err)
panic(err)
}
graphqlGroup.Any("", proxy.ProxyHandler(graphqlProxy))
graphqlGroup.Any("/", proxy.ProxyHandler(graphqlProxy))
}
// admin GraphQL 入口admin-portal 仍走独立路径以强制 admin 角色)
admin := r.Group("/api/admin")
admin.Use(middleware.CircuitBreaker("teacher-bff-admin"))
admin.Use(middleware.CircuitBreaker("apollo-router-admin"))
admin.Use(middleware.AuthMiddleware(cfg, fetcher))
admin.Use(middleware.AdminRoleMiddleware())
admin.Use(observability.Metrics())
{
// /api/admin/graphql 是唯一入口(契约 §2.3 要求),
// 路径重写:/api/admin/graphql → /graphqlteacher-bff @Controller("graphql")
graphqlProxy, err := proxy.NewProxyRewrite(cfg.TeacherBffURL, func(p string) string {
// 路径重写:/api/admin/graphql → /graphqlapollo-router
adminGraphqlProxy, err := proxy.NewProxyRewrite(cfg.ApolloRouterURL, func(p string) string {
return strings.TrimPrefix(p, "/api/admin")
})
if err != nil {
slog.Error("failed to create admin graphql proxy", "target", cfg.TeacherBffURL, "error", err)
slog.Error("failed to create admin graphql proxy", "target", cfg.ApolloRouterURL, "error", err)
panic(err)
}
admin.Any("/graphql", proxy.ProxyHandler(graphqlProxy))
admin.Any("/graphql", proxy.ProxyHandler(adminGraphqlProxy))
}
srv := &http.Server{
@@ -180,22 +192,5 @@ func registerProxy(api *gin.RouterGroup, prefix, targetURL string) {
api.Any("/"+prefix+"/*path", handler)
}
// registerBffProxy 创建带路径重写的反向代理,用于 BFF 路由teacher/student/parent
// BFF 在 /graphql 或 /v1/graphql 提供服务(@Controller与下游 controller 在 /v1/{domain}/* 的
// 非 BFF 服务不同,需剥离 /api/v1/{prefix} 前缀,仅转发剩余路径到下游。
// 例:/api/v1/teacher/graphql → /graphqlteacher-bff:3003/graphql
// 例:/api/v1/parent/v1/graphql → /v1/graphqlparent-bff:3010/v1/graphqlARB-022 §24.4 ISSUE-003 方案 A
// 契约依据student-portal_contract.md §2.3 /api/v1/{bff}/* → {bff}:port/*
func registerBffProxy(api *gin.RouterGroup, prefix, targetURL string) {
stripPrefix := "/api/v1/" + prefix
p, err := proxy.NewProxyRewrite(targetURL, func(p string) string {
return strings.TrimPrefix(p, stripPrefix)
})
if err != nil {
slog.Error("failed to create bff proxy", "prefix", prefix, "target", targetURL, "error", err)
panic(err)
}
handler := proxy.ProxyHandler(p)
api.Any("/"+prefix, handler)
api.Any("/"+prefix+"/*path", handler)
}
// registerBffProxy 已在 v2.1 M9 移除teacher-bff / student-bff / parent-bff 由 apollo-router 替代
// 前端 GraphQL 查询统一通过 /api/graphql → apollo-router:3000/graphql。