# ============================================================ # Docker Compose Override:本地开发态应用服务(v2.1) # # 用途:将 8 个应用服务(iam/classes/core-edu/content/msg/api-gateway/push-gateway/iam)也以容器方式运行, # 与 infra/docker-compose.yml 中的基础设施(MySQL/Redis/Kafka/Apollo Router/portal-shell)共用 default 网络。 # # 启动方式: # docker compose -f infra/docker-compose.yml -f infra/docker-compose.dev-apps.yml --profile p3 up -d # # 与 infra/docker-compose.yml 的关系: # - 同 name: edu-full(共用 project 命名空间与 default 网络) # - 应用服务通过容器名(edu-mysql/edu-redis/edu-kafka)访问基础设施 # - apollo-router 已在 infra/docker-compose.yml 定义,此处不重复 # - config-service 已在 infra/docker-compose.yml 定义,此处不重复 # # 注意:本文件只补充 docker-compose.yml 中缺失的 7 个应用服务(不含已定义的 config-service/apollo-router/portal-shell)。 # ============================================================ name: edu-full services: # ============================================================ # IAM 服务(NestJS,端口 3002 + gRPC 50052) # 依赖:MySQL(用户/角色/权限表)、Redis(缓存)、Kafka(Outbox 事件) # RS256 密钥:由 infra/keys/iam-{private,public}.pem 挂载(首次启动需生成,见 scripts/gen-iam-keys.ps1) # ============================================================ iam: build: context: .. dockerfile: services/iam/Dockerfile container_name: edu-iam restart: unless-stopped environment: PORT: "3002" GRPC_PORT: "50052" DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud REDIS_URL: redis://edu-redis:6379 KAFKA_BROKERS: kafka:29092 KAFKA_CLIENT_ID: iam-service IAM_PRIVATE_KEY_PATH: /keys/iam-private.pem IAM_PUBLIC_KEY_PATH: /keys/iam-public.pem JWT_ISSUER: ${JWT_ISSUER:-next-edu-cloud} JWT_AUDIENCE: ${JWT_AUDIENCE:-next-edu-cloud} JWT_KEY_ID: iam-rs256-v1 ACCESS_TOKEN_TTL: "15m" REFRESH_TOKEN_TTL_DAYS: "7" COOKIE_SECURE: "auto" OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} LOG_LEVEL: ${LOG_LEVEL:-info} NODE_ENV: production # DEV_MODE=true 让子图 middleware 跳过鉴权,允许 apollo-router rover introspect 子图 # 外网请求经 api-gateway(DEV_MODE=false,RS256 验签)入站,子图在 docker 内网可信 DEV_MODE: "true" ports: - "3002:3002" - "50052:50052" volumes: - ../infra/keys:/keys:ro depends_on: mysql: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3002/healthz"] interval: 15s timeout: 5s start_period: 30s retries: 10 # ============================================================ # Classes 服务(NestJS,端口 3001) # 依赖:MySQL、Redis、Kafka(可选) # ============================================================ classes: build: context: .. dockerfile: services/classes/Dockerfile container_name: edu-classes restart: unless-stopped environment: PORT: "3001" DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud REDIS_URL: redis://edu-redis:6379 KAFKA_BROKERS: kafka:29092 JWT_SECRET: ${JWT_SECRET:-p1-dev-secret-change-in-production} JWT_ISSUER: ${JWT_ISSUER:-next-edu-cloud} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} LOG_LEVEL: ${LOG_LEVEL:-info} NODE_ENV: production # DEV_MODE=true 让子图 middleware 跳过鉴权,允许 apollo-router rover introspect 子图 DEV_MODE: "true" ports: - "3001:3001" depends_on: mysql: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3001/healthz"] interval: 15s timeout: 5s start_period: 30s retries: 10 # ============================================================ # Core-Edu 服务(NestJS,端口 3004 + gRPC 50054) # 依赖:MySQL、Redis、Kafka(Outbox 事件) # ============================================================ core-edu: build: context: .. dockerfile: services/core-edu/Dockerfile container_name: edu-core-edu restart: unless-stopped environment: PORT: "3004" GRPC_PORT: "50054" DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud REDIS_URL: redis://edu-redis:6379 KAFKA_BROKERS: kafka:29092 OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} LOG_LEVEL: ${LOG_LEVEL:-info} NODE_ENV: production # DEV_MODE=true 让子图 middleware 跳过鉴权,允许 apollo-router rover introspect 子图 DEV_MODE: "true" ports: - "3004:3004" - "50054:50054" depends_on: mysql: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3004/healthz"] interval: 15s timeout: 5s start_period: 30s retries: 10 # ============================================================ # Content 服务(NestJS,端口 3005 + gRPC 50055) # 依赖:MySQL、Redis、Kafka(Outbox)、Neo4j(可选降级)、ES(可选降级) # ============================================================ content: build: context: .. dockerfile: services/content/Dockerfile container_name: edu-content restart: unless-stopped environment: PORT: "3005" GRPC_PORT: "50055" DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud REDIS_URL: redis://edu-redis:6379 KAFKA_BROKERS: kafka:29092 NEO4J_URL: bolt://neo4j:7687 NEO4J_USER: neo4j NEO4J_PASSWORD: ${NEO4J_PASSWORD:-changeme} ES_URL: http://elasticsearch:9200 OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} LOG_LEVEL: ${LOG_LEVEL:-info} NODE_ENV: production # DEV_MODE=true 让子图 middleware 跳过鉴权,允许 apollo-router rover introspect 子图 DEV_MODE: "true" ports: - "3005:3005" - "50055:50055" depends_on: mysql: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3005/healthz"] interval: 15s timeout: 5s start_period: 30s retries: 10 # ============================================================ # Msg 服务(NestJS,端口 3007 + gRPC 50057) # 依赖:MySQL、Redis、Kafka、ES(可选)、push-gateway(HTTP /internal/push) # ============================================================ msg: build: context: .. dockerfile: services/msg/Dockerfile container_name: edu-msg restart: unless-stopped environment: PORT: "3007" GRPC_PORT: "50057" DATABASE_URL: mysql://edu:${MYSQL_PASSWORD:-changeme}@edu-mysql:3306/next_edu_cloud REDIS_URL: redis://edu-redis:6379 KAFKA_BROKERS: kafka:29092 ES_URL: http://elasticsearch:9200 PUSH_GATEWAY_URL: http://push-gateway:8081 PUSH_INTERNAL_TOKEN: ${PUSH_INTERNAL_TOKEN:-edu-internal-token} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} LOG_LEVEL: ${LOG_LEVEL:-info} NODE_ENV: production # DEV_MODE=true 让子图 middleware 跳过鉴权,允许 apollo-router rover introspect 子图 DEV_MODE: "true" ports: - "3007:3007" - "50057:50057" depends_on: mysql: condition: service_healthy redis: condition: service_started push-gateway: condition: service_healthy healthcheck: test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3007/healthz"] interval: 15s timeout: 5s start_period: 30s retries: 10 # ============================================================ # Push-Gateway 服务(Go,端口 8081) # 依赖:Redis(Pub/Sub backplane)、JWKS(iam 公钥验签) # ============================================================ push-gateway: build: context: .. dockerfile: services/push-gateway/Dockerfile container_name: edu-push-gateway restart: unless-stopped environment: PUSH_GATEWAY_PORT: "8081" JWT_SECRET: ${JWT_SECRET:-p1-dev-secret-change-in-production} REDIS_URL: redis://edu-redis:6379 PUSH_INTERNAL_TOKEN: ${PUSH_INTERNAL_TOKEN:-edu-internal-token} JWKS_URL: http://iam:3002/v1/iam/.well-known/jwks.json WS_ALLOWED_ORIGINS: ${WS_ALLOWED_ORIGINS:-http://localhost:3000,http://localhost:4000,http://localhost:4001,http://localhost:4002,http://localhost:4003,http://localhost:4010} MAX_CONNS_PER_USER: "5" HEARTBEAT_INTERVAL_SECONDS: "30" OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} DEV_MODE: "false" ports: - "8081:8081" depends_on: redis: condition: service_started healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:8081/healthz"] interval: 15s timeout: 5s start_period: 10s retries: 5 # ============================================================ # API-Gateway 服务(Go,端口 8080) # 依赖:iam(JWKS)、apollo-router、所有后端服务(HTTP 路由代理) # ============================================================ api-gateway: build: context: .. dockerfile: services/api-gateway/Dockerfile container_name: edu-api-gateway restart: unless-stopped environment: API_GATEWAY_PORT: "8080" ENV: production JWT_SECRET: ${JWT_SECRET:-p1-dev-secret-change-in-production} JWT_ISSUER: ${JWT_ISSUER:-next-edu-cloud} JWT_AUDIENCE: ${JWT_AUDIENCE:-next-edu-cloud} IAM_JWKS_URL: http://iam:3002/v1/iam/.well-known/jwks.json DEV_MODE: "false" CLASSES_SERVICE_URL: http://classes:3001 IAM_SERVICE_URL: http://iam:3002 APOLLO_ROUTER_URL: http://apollo-router:3000 CORE_EDU_SERVICE_URL: http://core-edu:3004 CONTENT_SERVICE_URL: http://content:3005 MSG_SERVICE_URL: http://msg:3007 CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000,http://localhost:4000,http://localhost:4001,http://localhost:4002,http://localhost:4003,http://localhost:4010} OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://edu-jaeger:4318} LOG_LEVEL: ${LOG_LEVEL:-info} ports: - "8080:8080" depends_on: iam: condition: service_healthy apollo-router: condition: service_healthy healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:8080/healthz"] interval: 15s timeout: 5s start_period: 10s retries: 5 # ============================================================ # Apollo Router override(覆盖 docker-compose.yml 中的同名服务) # # 覆盖原因:默认 entrypoint.sh 会等待 ai/data-ana 子图(用户未启动), # 默认 supergraph.yaml 包含 ai/data-ana 子图(rover compose 会失败)。 # # 覆盖内容: # - 挂载 dev-apps-supergraph.yaml(仅 iam/core-edu/content/msg/config 5 个子图) # - 挂载 dev-apps-entrypoint.sh(仅等待 5 个子图) # - 添加 depends_on 5 个子图(health check 通过后才启动 router) # ============================================================ apollo-router: volumes: # 追加覆盖默认 supergraph.yaml 与 entrypoint.sh - ./apollo-router/dev-apps-supergraph.yaml:/dist/supergraph.yaml:ro - ./apollo-router/dev-apps-entrypoint.sh:/dist/entrypoint.sh:ro # 保留原 pq-manifest 挂载(来自 docker-compose.yml) - ../apps/portal-shell/public/pq-manifest.json:/etc/apollo-router/pq-manifest.json:ro depends_on: iam: condition: service_healthy config-service: condition: service_healthy core-edu: condition: service_healthy content: condition: service_healthy msg: condition: service_healthy