Files
Edu/docs/runbooks/local-stack.md
SpecialX 9cedf0c437 feat(portal-shell): v2.0 P0 shadcn standardization + security + streaming + error handling
- shadcn/ui 标准化:废弃纸感令牌,统一 bg-background/text-foreground 等
- Tailwind v4 + @theme inline,移除 tailwind.config.js
- React 19 use() + Suspense 流式渲染,首屏骨架秒出
- 三级错误边界:Route → Section → Widget 层层兜底
- 错误上报:useErrorReport → sendBeacon → /api/log mock 端点
- 三层安全边界:L1 角色门禁 / L2 权限点门禁 / L3 数据范围
- 权限位图 base36 压缩:67 权限点 → ~14 字符,JWT 体积减少 ≥ 99%
- notify 统一 Toast 封装,禁止业务直接 import sonner
- PluginBoundary 替代 PluginLoader(错误边界 + Suspense + Skeleton 三件套)

验证:typecheck 0 错误 / lint 0 错误 / build 6 路由生成成功
2026-07-17 16:10:05 +08:00

14 KiB
Raw Permalink Blame History

本地全栈启动运维手册

版本v2.1(混合部署模式) 日期2026-07-17 适用范围Edu v2.1 架构本地开发与测试环境


1. 架构概览

1.1 部署模式

混合部署:基础设施 + Apollo Router 用 Docker应用服务本地运行。

层级 运行方式 说明
基础设施 Docker Compose MySQL/Redis/Kafka/ClickHouse/Neo4j/ES 等
Apollo Router Docker 容器 联邦 GraphQL 网关(组合 5 个子图,端口 3000
应用服务 本地进程 NestJSpnpm exec nest start/ Pythonuvicorn/ Gogo run
前端 本地进程 Next.js dev serverportal-shell

Apollo Router 在 Docker 容器中运行,通过 host.docker.internal 访问 host 上的 5 个 Federation 2 子图iam/core-edu/content/msg/config-servicecompose 出 supergraph 后对外暴露 http://localhost:3000/graphql

1.2 服务端口映射

基础设施Docker

服务 端口 容器名
MySQL 3306 edu-mysql
Redis 6379 edu-redis
Kafka 9092 edu-kafka
Zookeeper 2181 edu-zookeeper
ClickHouse 8123 edu-clickhouse
Neo4j 7474/7687 edu-neo4j
Elasticsearch 9200 edu-es
Debezium 8083 edu-debezium
Apollo Router 3000/8088 edu-apollo-router
Jaeger 16686 edu-jaeger
Prometheus 9090 edu-prometheus
Grafana 3030 edu-grafana
Alertmanager 9093 edu-alertmanager
Loki 3100 edu-loki

应用服务(本地)

服务 端口 gRPC 端口 类型 健康端点
iam 3002 50052 NestJS /healthz
config-service 3011 50059 NestJS /healthz
classes 3001 50053 NestJS /healthz
core-edu 3004 50054 NestJS /healthz
content 3005 50055 NestJS /healthz
msg 3007 50056 NestJS /healthz
data-ana 3006 - Python /healthz
ai 3008 - Python /healthz
api-gateway 8080 - Go /healthz
push-gateway 8081 - Go /healthz
portal-shell 4010 - Next.js /api/health

2. 一键脚本使用

2.1 一键启动

# 启动全部(基础设施 + 应用服务 + Apollo Router
.\scripts\start-all.ps1

# 仅启动基础设施
.\scripts\start-all.ps1 -SkipApps

# 仅启动应用服务(基础设施已运行)
.\scripts\start-all.ps1 -SkipInfra

# 自动杀掉占用端口的进程(不交互确认)
.\scripts\start-all.ps1 -Force

# 跳过 Apollo Router仅用 config-service 直连,调试子图时使用)
.\scripts\start-all.ps1 -SkipRouter

启动流程8 阶段):

  1. 加载 .env 文件
  2. 启动 Docker 基础设施p3+p5 profile + observability
  3. 基础设施健康检查
  4. 端口冲突检查与清理
  5. 确保 @edu/shared-ts 已编译
  6. 清理 NestJS tsbuildinfo 缓存
  7. 启动应用服务 + 健康检查
  8. 启动 Apollo RouterDocker 容器,组合 5 子图 supergraph

Apollo Router 依赖 5 个 Federation 2 子图iam/core-edu/content/msg/config-service就绪后才能 compose supergraph因此在第 7 步应用服务健康检查通过后才启动。如果跳过 -SkipRouterportal-shell 会自动降级到 config-service 直连(http://localhost:3011

2.2 一键关闭

# 关闭应用服务 + Apollo Router
.\scripts\stop-all.ps1

# 强制按端口杀进程(窗口关闭后进程残留时使用)
.\scripts\stop-all.ps1 -KillByPort

# 关闭应用 + Apollo Router + Docker 基础设施
.\scripts\stop-all.ps1 -IncludeDocker

# 全部关闭
.\scripts\stop-all.ps1 -KillByPort -IncludeDocker

# 仅关闭应用,保留 Router 容器
.\scripts\stop-all.ps1 -SkipRouter

关闭顺序Apollo Router 容器 → 应用服务窗口 → 按端口杀残留 → Docker 基础设施。先关 Router 避免子图关闭时 router 刷连接错误日志。

2.3 典型工作流

# 早晨开始工作:一键启动
.\scripts\start-all.ps1

# 中午休息:仅关闭应用(保留 Docker 数据)
.\scripts\stop-all.ps1 -KillByPort

# 下午继续:仅启动应用
.\scripts\start-all.ps1 -SkipInfra -Force

# 下班:全部关闭
.\scripts\stop-all.ps1 -KillByPort -IncludeDocker

3. 手动操作

3.1 仅启动基础设施

cd infra
# 基础设施服务(跳过需构建镜像的 app 服务)
docker compose -f docker-compose.yml --profile p3 --profile p5 up -d mysql redis kafka zookeeper clickhouse neo4j elasticsearch debezium-connect
# 可观测性栈
docker compose -f docker-compose.yml --profile observability up -d

3.2 单独启动某个应用服务

# NestJS 服务
pnpm --filter @edu/iam-service exec nest start
pnpm --filter @edu/config-service exec nest start
pnpm --filter @edu/classes-service exec nest start
pnpm --filter @edu/core-edu-service exec nest start
pnpm --filter @edu/content-service exec nest start
pnpm --filter @edu/msg-service exec nest start

# Python 服务
cd services\data-ana; uv run uvicorn data_ana.main:app --app-dir src --host 0.0.0.0 --port 3006
cd services\ai; uv run uvicorn ai.main:app --app-dir src --host 0.0.0.0 --port 3008

# Go 服务
cd services\api-gateway; go run .
cd services\push-gateway; go run .

# 前端
pnpm --filter @edu/portal-shell dev

3.3 健康检查

# 单个服务
curl http://localhost:3002/healthz   # iam
curl http://localhost:4010/api/health # portal-shell

# 批量健康检查脚本
.\scripts\health-check.ps1

3.4 查看 Docker 容器状态

# 运行中的容器
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# 查看容器日志
docker logs edu-mysql --tail 50
docker logs edu-kafka --tail 50 -f

# 容器健康状态
docker inspect -f '{{.State.Health.Status}}' edu-mysql

3.5 手动启动 Apollo Router

Apollo Router 通常由 start-all.ps1 自动启动。如需单独启动(例如调试 router 配置):

# 前置5 个 Federation 2 子图必须先在 host 上运行
#   iam (3002) / core-edu (3004) / content (3005) / msg (3007) / config-service (3011)
# 验证子图rover subgraph introspect http://localhost:3002/graphql
#   返回应包含 @key、@link、_service 字段

# 启动 Router 容器dev 模式,子图在 host 上)
docker rm -f edu-apollo-router 2>&1 | Out-Null
docker run -d --name edu-apollo-router `
    --add-host=host.docker.internal:host-gateway `
    -p 3000:3000 -p 8088:8088 `
    -v e:\Desktop\Edu\infra\apollo-router\dev-supergraph.yaml:/dist/supergraph.yaml `
    -v e:\Desktop\Edu\infra\apollo-router\dev-entrypoint.sh:/dist/entrypoint.sh `
    -v e:\Desktop\Edu\infra\apollo-router\router.yaml:/dist/configuration.yaml `
    -e ROUTER_AUTH_SECRET=dev-router-secret `
    -e APOLLO_ELV2_LICENSE=accept `
    -e HTTP_PROXY=http://host.docker.internal:7897 `
    -e HTTPS_PROXY=http://host.docker.internal:7897 `
    -e NO_PROXY=localhost,127.0.0.1,host.docker.internal `
    edu/apollo-router:dev `
    /dist/entrypoint.sh

# 验证
curl http://localhost:8088/health            # {"status":"UP"}
curl -X POST http://localhost:3000/graphql `
    -H "Content-Type: application/json" `
    -d '{\"query\":\"{ __schema { queryType { fields { name } } } }\"}'

# 查看日志supergraph compose 过程 + router 启动)
docker logs edu-apollo-router -f

首次启动需 docker build -t edu/apollo-router:dev infra/apollo-router/ 构建镜像(包含 rover CLI 和预下载的 supergraph 插件)。HTTP_PROXY 用于容器内 rover 下载 supergraph 插件(如已预下载到镜像则可省略)。


4. 环境变量

4.1 必需的 .env 变量

根目录 .env 文件必须包含以下变量:

# 数据库
DATABASE_URL=mysql://edu:changeme@localhost:3306/next_edu_cloud
MYSQL_ROOT_PASSWORD=changeme
MYSQL_DATABASE=next_edu_cloud
MYSQL_USER=edu
MYSQL_PASSWORD=changeme

# Redis
REDIS_URL=redis://localhost:6379

# Kafka
KAFKA_BROKERS=localhost:9092

# JWT 密钥路径
IAM_PRIVATE_KEY_PATH=E:\Desktop\Edu\keys\iam-private.pem
IAM_PUBLIC_KEY_PATH=E:\Desktop\Edu\keys\iam-public.pem

# 开发模式(绕过 JWT 校验,接受 dev-token
DEV_MODE=true

# Router 认证密钥(子图校验 Apollo Router 请求)
ROUTER_AUTH_SECRET=dev-router-secret

# Neo4j
NEO4J_PASSWORD=changeme

# 可观测性
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

4.2 开发模式DEV_MODE=true

  • 绕过 JWT 校验
  • 接受 Authorization: Bearer dev-token 作为有效令牌
  • 预定义用户角色

4.3 env-loader 机制

NestJS 服务通过 @edu/shared-ts/env-loader 在启动时自动从 monorepo 根加载 .env 文件,解决 PowerShell → pnpm → nest 子进程环境变量丢失问题。所有 NestJS 服务的 main.ts 顶部已添加:

import "@edu/shared-ts/env-loader";

5. 前置准备

5.1 首次运行

# 1. 安装依赖
pnpm install --no-frozen-lockfile

# 2. 编译 shared-ts其他服务依赖它
pnpm --filter @edu/shared-ts build

# 3. 生成 JWT 密钥(如不存在)
mkdir keys
openssl genrsa -out keys/iam-private.pem 2048
openssl rsa -in keys/iam-private.pem -pubout -out keys/iam-public.pem

# 4. 配置 .env从 .env.example 复制并修改)
cp .env.example .env

5.2 工具链要求

工具 版本 用途
Node.js 22+ NestJS / Next.js
pnpm 11+ 包管理
Go 1.22+ api-gateway / push-gateway
uv latest Python 服务data-ana / ai
Docker 25+ 基础设施容器
PowerShell 7+ 启动脚本

6. 访问入口

6.1 应用入口

入口 URL 说明
Portal Shell http://localhost:4010 统一前端
API Gateway http://localhost:8080 REST API 网关
IAM GraphQL http://localhost:3002/graphql IAM 子图(需 Router Auth

6.2 监控入口

入口 URL 登录
Grafana http://localhost:3030 admin / admin
Jaeger http://localhost:16686 无需登录
Prometheus http://localhost:9090 无需登录
Alertmanager http://localhost:9093 无需登录

6.3 基础设施管理

服务 URL / 端口 登录
MySQL localhost:3306 edu / changeme
Redis localhost:6379 无密码
Neo4j http://localhost:7474 neo4j / changeme
ClickHouse http://localhost:8123 default / (无密码)
Elasticsearch http://localhost:9200 无需认证

7. 故障排查

7.1 常见问题

问题 解决方案
端口被占用 .\scripts\stop-all.ps1 -KillByPortstart-all.ps1 -Force
NestJS 环境变量缺失 确认 .env 存在且 main.ts 顶部有 import "@edu/shared-ts/env-loader"
shared-ts 找不到 pnpm --filter @edu/shared-ts build
pnpm install 失败lockfile pnpm install --no-frozen-lockfile
Docker 容器启动失败 docker logs <container> 查看日志
MySQL 连接失败 确认 edu-mysql 容器 healthydocker ps
Kafka 连接失败 确认用 localhost:9092OUTSIDE listener
前端首页超时 Next.js dev 首次编译慢,等待 30 秒后重试
GraphQL 子图返回 401 子图需 Router-Authorization headerADR-036非直接访问

7.2 日志查看

# Docker 容器日志
docker logs edu-mysql --tail 100 -f
docker logs edu-kafka --tail 100 -f

# 应用服务日志
# 查看 edu-app-* 命名的 PowerShell 窗口

7.3 重置环境

# 完全重置(删除所有数据)
.\scripts\stop-all.ps1 -KillByPort -IncludeDocker
cd infra
docker compose -f docker-compose.yml --profile p3 --profile p5 --profile observability down -v
cd ..
.\scripts\start-all.ps1

7.4 Temporal 服务(可选)

Temporal 服务用于 AI 工作流引擎ADR-030当前未包含在一键启动脚本中。如需启动

cd infra
docker compose -f docker-compose.yml --profile p3 up -d temporal-postgresql temporal temporal-ui
# 注意temporal auto-setup 镜像需调整 DB 环境变量为 postgres当前配置有已知问题

8. 架构约束

以下约束来自 project_rules.md 与 ADR脚本已内置遵守。

  • 混合部署:基础设施 Docker + 应用本地,避免应用镜像构建失败
  • GraphQL 子图隔离:子图仅接受 Apollo Router 请求(带 Router-Authorization header不直接对外
  • gRPC 内部通信:服务间通过 gRPC50052-50059不通过 GraphQL
  • CDC + Outbox:业务代码写 outbox 表Debezium 监听 binlog 投递 Kafka
  • DEV_MODE:开发模式绕过 JWT 校验,仅限本地开发