fix: 修复集成测试中发现的全部 bug — 15 服务端到端验证通过

修复涵盖 6 大类问题:

1. api-gateway
   - 路径前缀剥离 /api 而非 /api/v1,保留下游 /v1/ controller 前缀
   - JWKS URL 默认值修复
   - publicPaths 白名单对齐 /v1/iam/*

2. iam
   - iam.module.ts exports 补充 PermissionCacheService 和 IamRepository
   - main.ts resolveProtoPath() 多路径探测 proto 文件

3. core-edu
   - app.module.ts AuthMiddleware 全局注册

4. BFF 层 GraphQL 端点(teacher-bff / parent-bff / student-bff)
   - teacher-bff: mock dataScope OWN→SELF 对齐 GraphQL enum;WHATWG Request header .get() 提取
   - parent-bff: handleNodeRequestAndResponse 不存在 → 直接 yoga(req,res);WHATWG Request header .get() 提取
   - student-bff: auth.resolver 移除 ActionState 信封返回扁平对象;WHATWG Request header .get() 提取

5. ai
   - Kafka 事务降级 + 10s 超时
   - gRPC 拦截器降级
   - dev mode 禁用事务模式

6. 前端 + 共享包
   - teacher-portal: MF 插件条件实例化 + transpilePackages + extensionAlias
   - ui-components: error-boundary.tsx 添加 use client
   - ui-tokens: tailwind-theme.css 移除 @layer base
   - shared-ts: 导出从源码改为 dist 编译产物;OutboxModule global:true

7. infra
   - .gitignore 补充 keys/ *.pem *.key secrets/ 排除规则
   - infra/init-sql/02-all-services-schema.sql 36 张表 DDL

验证结果:
- TS typecheck: 19 个 workspace 项目全部通过
- Go vet + Ruff: 通过
- 15 服务全部启动成功
- 3 个 BFF GraphQL 端点 + 4 个前端页面全部 200
- Gateway → iam → core-edu 端到端链路验证通过

AI identity: trae-main(集成测试修复会话)
This commit is contained in:
SpecialX
2026-07-11 01:41:46 +08:00
parent c6362f4b04
commit 61d824924a
35 changed files with 1020 additions and 192 deletions

View File

@@ -11,7 +11,6 @@
*/
import type { StudentBffContext } from "../../shared/graphql/yoga.js";
import type { DownstreamResponse } from "@edu/shared-ts/bff";
import { ok, fail, degraded, DegradedReason } from "../../shared/action-state.js";
import { UnauthorizedError } from "../../shared/errors/application-error.js";
export const authResolvers = {
@@ -35,19 +34,28 @@ export const authResolvers = {
service: "iam",
method: "GetUserInfo",
request: { userId: ctx.userId },
options: { traceId: ctx.traceId, metadata: { "x-user-id": ctx.userId } },
options: {
traceId: ctx.traceId,
metadata: { "x-user-id": ctx.userId },
},
},
{
service: "iam",
method: "GetEffectivePermissions",
request: { userId: ctx.userId },
options: { traceId: ctx.traceId, metadata: { "x-user-id": ctx.userId } },
options: {
traceId: ctx.traceId,
metadata: { "x-user-id": ctx.userId },
},
},
{
service: "iam",
method: "GetViewports",
request: { userId: ctx.userId },
options: { traceId: ctx.traceId, metadata: { "x-user-id": ctx.userId } },
options: {
traceId: ctx.traceId,
metadata: { "x-user-id": ctx.userId },
},
},
] as const);
@@ -59,23 +67,13 @@ export const authResolvers = {
const degradedFields: string[] = [];
if (!userInfoResp.success) degradedFields.push("user");
if (!permsResp.success) degradedFields.push("permissions");
if (!viewportsResp.success) degradedFields.push("viewport");
if (!permsResp.success) degradedFields.push("effectivePermissions");
if (!viewportsResp.success) degradedFields.push("viewports");
// 必需字段失败时返回错误
// 必需字段失败时抛错
if (!userInfoResp.success) {
return fail(
"BFF_STUDENT_BAD_GATEWAY",
throw new Error(
`Failed to fetch user info: ${userInfoResp.error.message}`,
{
details: {
service: userInfoResp.error.service,
method: userInfoResp.error.method,
traceId: ctx.traceId,
},
i18nKey: "error.bffStudent.bad_gateway",
traceId: ctx.traceId,
},
);
}
@@ -93,7 +91,8 @@ export const authResolvers = {
? (viewportsResp.data as { navigation: unknown[]; dataScope: unknown })
: { navigation: [], dataScope: {} };
const data = {
// 返回扁平对象,对齐 CurrentUserPayload schema
return {
user: {
id: userInfo.userId,
email: userInfo.email,
@@ -101,20 +100,14 @@ export const authResolvers = {
avatar: userInfo.avatar,
roles: userInfo.roles,
},
permissions,
viewport: viewports,
viewports,
effectivePermissions: permissions,
dataScope: "SELF",
degraded: degradedFields.length > 0,
degradedReason:
degradedFields.length > 0 ? "DOWNSTREAM_PARTIAL_FAILURE" : null,
degradedFields: degradedFields.length > 0 ? degradedFields : null,
};
if (degradedFields.length > 0) {
return degraded(
data,
DegradedReason.DOWNSTREAM_PARTIAL_FAILURE,
degradedFields,
{ traceId: ctx.traceId },
);
}
return ok(data, { traceId: ctx.traceId });
},
},
};