# 本地全栈启动运维手册 > 版本: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) | | 应用服务 | 本地进程 | NestJS(pnpm exec nest start)/ Python(uvicorn)/ Go(go run) | | 前端 | 本地进程 | Next.js dev server(portal-shell) | > Apollo Router 在 Docker 容器中运行,通过 `host.docker.internal` 访问 host 上的 5 个 Federation 2 子图(iam/core-edu/content/msg/config-service),compose 出 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 一键启动 ```powershell # 启动全部(基础设施 + 应用服务 + 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 Router(Docker 容器,组合 5 子图 supergraph) > Apollo Router 依赖 5 个 Federation 2 子图(iam/core-edu/content/msg/config-service)就绪后才能 compose supergraph,因此在第 7 步应用服务健康检查通过后才启动。如果跳过 `-SkipRouter`,portal-shell 会自动降级到 config-service 直连(http://localhost:3011)。 ### 2.2 一键关闭 ```powershell # 关闭应用服务 + 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 典型工作流 ```powershell # 早晨开始工作:一键启动 .\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 仅启动基础设施 ```powershell 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 单独启动某个应用服务 ```powershell # 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 健康检查 ```powershell # 单个服务 curl http://localhost:3002/healthz # iam curl http://localhost:4010/api/health # portal-shell # 批量健康检查脚本 .\scripts\health-check.ps1 ``` ### 3.4 查看 Docker 容器状态 ```powershell # 运行中的容器 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 配置): ```powershell # 前置: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` 文件必须包含以下变量: ```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` 顶部已添加: ```typescript import "@edu/shared-ts/env-loader"; ``` --- ## 5. 前置准备 ### 5.1 首次运行 ```powershell # 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 -KillByPort` 或 `start-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 ` 查看日志 | | MySQL 连接失败 | 确认 `edu-mysql` 容器 healthy:`docker ps` | | Kafka 连接失败 | 确认用 `localhost:9092`(OUTSIDE listener) | | 前端首页超时 | Next.js dev 首次编译慢,等待 30 秒后重试 | | GraphQL 子图返回 401 | 子图需 `Router-Authorization` header(ADR-036),非直接访问 | ### 7.2 日志查看 ```powershell # Docker 容器日志 docker logs edu-mysql --tail 100 -f docker logs edu-kafka --tail 100 -f # 应用服务日志 # 查看 edu-app-* 命名的 PowerShell 窗口 ``` ### 7.3 重置环境 ```powershell # 完全重置(删除所有数据) .\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),当前未包含在一键启动脚本中。如需启动: ```powershell 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 内部通信**:服务间通过 gRPC(50052-50059),不通过 GraphQL - **CDC + Outbox**:业务代码写 outbox 表,Debezium 监听 binlog 投递 Kafka - **DEV_MODE**:开发模式绕过 JWT 校验,仅限本地开发