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

412 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地全栈启动运维手册
> 版本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 一键启动
```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 RouterDocker 容器,组合 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 <container>` 查看日志 |
| MySQL 连接失败 | 确认 `edu-mysql` 容器 healthy`docker ps` |
| Kafka 连接失败 | 确认用 `localhost:9092`OUTSIDE listener |
| 前端首页超时 | Next.js dev 首次编译慢,等待 30 秒后重试 |
| GraphQL 子图返回 401 | 子图需 `Router-Authorization` headerADR-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 内部通信**:服务间通过 gRPC50052-50059不通过 GraphQL
- **CDC + Outbox**:业务代码写 outbox 表Debezium 监听 binlog 投递 Kafka
- **DEV_MODE**:开发模式绕过 JWT 校验,仅限本地开发