| @@ -0,0 +1,18 @@ | |||
| # 打包产物 | |||
| isolated/dist/ | |||
| *.tar | |||
| *.tar.gz | |||
| *.tgz | |||
| # 真实环境变量文件,只提交 .env.example | |||
| .env | |||
| *.env | |||
| !.env.example | |||
| !**/.env.example | |||
| # 临时文件 | |||
| *.log | |||
| *.tmp | |||
| .DS_Store | |||
| Thumbs.db | |||
| @@ -0,0 +1,154 @@ | |||
| # EMP 隔离测试环境变量模板 | |||
| # 使用方式:复制为 .env 后按目标服务器实际情况修改。 | |||
| # ----------------------------------------------------------------------------- | |||
| # 镜像与项目名称 | |||
| # ----------------------------------------------------------------------------- | |||
| COMPOSE_PROJECT_NAME=emp-test | |||
| CONTAINER_PREFIX=emp-test | |||
| IMAGE_NAMESPACE=emp-test | |||
| IMAGE_TAG=latest | |||
| # 服务器外网 IP 或域名。 | |||
| # Kafka 对外访问会把这个地址写入 advertised.listeners,外部客户端必须能访问它。 | |||
| PUBLIC_HOST=127.0.0.1 | |||
| # ----------------------------------------------------------------------------- | |||
| # 对外端口 | |||
| # ----------------------------------------------------------------------------- | |||
| # 前端访问:http://PUBLIC_HOST:ADMIN_HOST_PORT | |||
| ADMIN_HOST_PORT=4081 | |||
| GATEWAY_HOST_PORT=9000 | |||
| WS_HOST_PORT=3000 | |||
| PDF_HOST_PORT=3100 | |||
| NACOS_HOST_PORT=9008 | |||
| NACOS_GRPC_HOST_PORT=10008 | |||
| # MySQL / Kafka / TDengine 需要开放到宿主机,便于 Navicat、Kafka 客户端、TDengine Web/REST 调试。 | |||
| # 如果宿主机端口已被其他项目占用,改这里即可。 | |||
| MYSQL_HOST_PORT=13306 | |||
| KAFKA_HOST_PORT=19094 | |||
| TDENGINE_HOST_PORT=6030 | |||
| TDENGINE_REST_HOST_PORT=6041 | |||
| TDENGINE_RPC_HOST_PORT=6043 | |||
| TDENGINE_RPC_UDP_HOST_PORT=6044 | |||
| TDENGINE_KEEPER_HOST_PORT=6060 | |||
| # Redis 默认只绑定本机,避免直接暴露公网;确实需要外部访问再改成 0.0.0.0。 | |||
| REDIS_BIND_HOST=127.0.0.1 | |||
| REDIS_HOST_PORT=16379 | |||
| # ----------------------------------------------------------------------------- | |||
| # 中间件镜像 | |||
| # ----------------------------------------------------------------------------- | |||
| MYSQL_IMAGE=mysql:8.0 | |||
| REDIS_IMAGE=redis:7-alpine | |||
| KAFKA_IMAGE=bitnami/kafka:3.7.0 | |||
| TDENGINE_IMAGE=tdengine/tdengine:3.3.6.0 | |||
| NACOS_IMAGE=nacos/nacos-server:v2.3.2-slim | |||
| # ----------------------------------------------------------------------------- | |||
| # MySQL 8.0 | |||
| # ----------------------------------------------------------------------------- | |||
| MYSQL_DATABASE=emp | |||
| MYSQL_ROOT_PASSWORD=change-me-mysql-root | |||
| DB_URL=jdbc:mysql://mysql:3306/emp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai | |||
| DB_USER=root | |||
| DB_PWD=change-me-mysql-root | |||
| # 给 emp_ws 模拟器读取车辆档案使用。 | |||
| SIMULATOR_DB_HOST=mysql | |||
| SIMULATOR_DB_PORT=3306 | |||
| SIMULATOR_DB_USER=root | |||
| SIMULATOR_DB_PASSWORD=change-me-mysql-root | |||
| SIMULATOR_DB_DATABASE=emp | |||
| SIMULATOR_DB_LIMIT=0 | |||
| # ----------------------------------------------------------------------------- | |||
| # Redis | |||
| # ----------------------------------------------------------------------------- | |||
| REDIS_HOST=redis | |||
| REDIS_PORT=6379 | |||
| REDIS_PASSWORD=change-me-redis | |||
| REDIS_DB=0 | |||
| # ----------------------------------------------------------------------------- | |||
| # Kafka | |||
| # ----------------------------------------------------------------------------- | |||
| KAFKA_BROKERS=kafka:9092 | |||
| KAFKA_GROUP_ID=ecmp-data-group-v2 | |||
| KAFKA_TOPIC=vehicle-data | |||
| KAFKA_USER= | |||
| KAFKA_PWD= | |||
| SIMULATOR_KAFKA_BROKERS=kafka:9092 | |||
| SIMULATOR_KAFKA_TOPIC=vehicle-data | |||
| SIMULATOR_KAFKA_USER= | |||
| SIMULATOR_KAFKA_PASSWORD= | |||
| SIMULATOR_KAFKA_CLIENT_ID=emp-simulator | |||
| SIMULATOR_KAFKA_BATCH_SIZE=500 | |||
| # ----------------------------------------------------------------------------- | |||
| # TDengine | |||
| # ----------------------------------------------------------------------------- | |||
| TDENGINE_DATABASE=emp | |||
| TDENGINE_USER=root | |||
| TDENGINE_PWD=taosdata | |||
| TDENGINE_URL=jdbc:TAOS-RS://tdengine:6041/emp | |||
| # ----------------------------------------------------------------------------- | |||
| # Nacos | |||
| # ----------------------------------------------------------------------------- | |||
| NACOS_ADDR=nacos:8848 | |||
| NACOS_USER=nacos | |||
| NACOS_PWD=nacos | |||
| NACOS_AUTH_ENABLE=true | |||
| NACOS_AUTH_IDENTITY_KEY=emp | |||
| NACOS_AUTH_IDENTITY_VALUE=emp2026 | |||
| NACOS_AUTH_TOKEN=ZW1wLXBsYXRmb3JtLW5hY29zLXNlY3JldC1rZXktMjAyNg== | |||
| # ----------------------------------------------------------------------------- | |||
| # 后端通用配置 | |||
| # ----------------------------------------------------------------------------- | |||
| SPRING_PROFILES_ACTIVE=prod | |||
| JWT_SECRET=emp-platform-secret-key-2026-yjfs | |||
| JWT_EXPIRATION=86400000 | |||
| SCHEDULER_ENABLED=true | |||
| # ----------------------------------------------------------------------------- | |||
| # WebSocket / 模拟器 | |||
| # ----------------------------------------------------------------------------- | |||
| EMP_WS_ENV=production | |||
| NODE_ENV=production | |||
| PORT=3000 | |||
| WS_INSTANCES=1 | |||
| WS_HOST=emp-ws | |||
| SERVER_API_BASE_URL=http://emp-gateway:9000/api | |||
| SIMULATOR_ADMIN_USERNAME=admin | |||
| SIMULATOR_LOGIN_AUTH=88871fe697e860463cd062cf3705b16f | |||
| SIMULATOR_JWT_SECRET=emp-platform-secret-key-2026-yjfs | |||
| # ----------------------------------------------------------------------------- | |||
| # PDF 与前端地址 | |||
| # ----------------------------------------------------------------------------- | |||
| PDF_SERVICE_URL=http://emp-pdf:3100 | |||
| PDF_FRONTEND_BASE_URL=http://127.0.0.1:4081 | |||
| # ----------------------------------------------------------------------------- | |||
| # 第三方配置,按需填写 | |||
| # ----------------------------------------------------------------------------- | |||
| AMAP_KEY= | |||
| COS_SECRET_ID=change-me | |||
| COS_SECRET_KEY=change-me | |||
| COS_REGION=ap-chengdu | |||
| COS_BUCKET=emp-example-bucket | |||
| SYNC_BASE_URL=https://example.com | |||
| SYNC_TK=change-me | |||
| SYNC_TENANT_ID=change-me | |||
| SYNC_REPORT_CRON=0 30 2 * * ? | |||
| SYNC_REPORT_SYNC_ENABLED=false | |||
| SYNC_REPORT_SYNC_CONCURRENCY=3 | |||
| SYNC_REPORT_SYNC_GROUP_NAMES= | |||
| SYNC_REPORT_CACHE_MISS_FETCH_ENABLED=false | |||
| GROUP_REPORT_CRON=0 30 4 ? * THU,SUN | |||
| @@ -0,0 +1,188 @@ | |||
| # EMP 隔离测试环境部署说明 | |||
| 这套部署方案用于“服务器不放源码,只运行本地打好的 Docker 镜像包”的场景。 | |||
| ## 目录说明 | |||
| 本地项目中: | |||
| ```text | |||
| deploy/isolated/ | |||
| build-package.ps1 # Windows 本地打包 | |||
| build-package.sh # Linux / macOS / WSL 本地打包 | |||
| docker-compose.runtime.yml # 服务器运行用 compose 模板 | |||
| .env.example # 服务器运行配置模板 | |||
| install.sh # 服务器安装脚本 | |||
| dockerfiles/ # 本地构建镜像用 Dockerfile | |||
| nginx/admin.conf # 前端 nginx 代理配置 | |||
| ``` | |||
| 打包后产物在: | |||
| ```text | |||
| deploy/isolated/dist/emp-test-runtime-时间戳.tar.gz | |||
| ``` | |||
| 服务器解压后只需要: | |||
| ```text | |||
| docker-compose.yml | |||
| .env | |||
| install.sh | |||
| images.tar | |||
| README.md | |||
| ``` | |||
| ## 本地打包 | |||
| Windows PowerShell: | |||
| ```powershell | |||
| cd E:\emp\deploy\isolated | |||
| .\build-package.ps1 | |||
| ``` | |||
| Linux / WSL: | |||
| ```bash | |||
| cd /path/to/emp/deploy/isolated | |||
| sh build-package.sh | |||
| ``` | |||
| 如果本机已经构建好所有业务镜像,只想重新生成安装包: | |||
| ```bash | |||
| SKIP_BUILD=1 sh build-package.sh | |||
| ``` | |||
| 国内 npm 慢时可以指定源: | |||
| ```bash | |||
| NPM_REGISTRY=https://registry.npmmirror.com sh build-package.sh | |||
| ``` | |||
| 默认会把 MySQL、Redis、Kafka、TDengine、Nacos 的中间件镜像一起打进 `images.tar`,服务器不需要访问 Docker Hub。 | |||
| ## 上传服务器 | |||
| ```bash | |||
| scp deploy/isolated/dist/emp-test-runtime-*.tar.gz root@服务器IP:/opt/ | |||
| ``` | |||
| 服务器执行: | |||
| ```bash | |||
| mkdir -p /opt/emp-test | |||
| tar -xzf /opt/emp-test-runtime-*.tar.gz -C /opt/emp-test --strip-components=1 | |||
| cd /opt/emp-test | |||
| cp .env.example .env | |||
| vi .env | |||
| sh install.sh | |||
| ``` | |||
| ## 必改配置 | |||
| `.env` 中至少要改这些: | |||
| ```env | |||
| PUBLIC_HOST=服务器外网IP或域名 | |||
| MYSQL_ROOT_PASSWORD=强密码 | |||
| DB_PWD=同 MYSQL_ROOT_PASSWORD | |||
| SIMULATOR_DB_PASSWORD=同 MYSQL_ROOT_PASSWORD | |||
| REDIS_PASSWORD=强密码 | |||
| PDF_FRONTEND_BASE_URL=http://服务器外网IP:4081 | |||
| ``` | |||
| 如果端口被宿主机其他项目占用,改这些端口: | |||
| ```env | |||
| ADMIN_HOST_PORT=4081 | |||
| GATEWAY_HOST_PORT=9000 | |||
| MYSQL_HOST_PORT=13306 | |||
| KAFKA_HOST_PORT=19094 | |||
| TDENGINE_REST_HOST_PORT=6041 | |||
| ``` | |||
| ## 对外连接 | |||
| MySQL 8.0: | |||
| ```text | |||
| host: 服务器外网IP | |||
| port: MYSQL_HOST_PORT,默认 13306 | |||
| user: root | |||
| password: MYSQL_ROOT_PASSWORD | |||
| database: emp | |||
| ``` | |||
| Kafka: | |||
| ```text | |||
| bootstrap.servers=PUBLIC_HOST:KAFKA_HOST_PORT | |||
| 默认端口:19094 | |||
| ``` | |||
| TDengine REST: | |||
| ```text | |||
| http://PUBLIC_HOST:TDENGINE_REST_HOST_PORT | |||
| 默认端口:6041 | |||
| ``` | |||
| Nacos: | |||
| ```text | |||
| http://PUBLIC_HOST:NACOS_HOST_PORT/nacos | |||
| 默认账号:nacos | |||
| 默认密码:nacos | |||
| ``` | |||
| 前端: | |||
| ```text | |||
| http://PUBLIC_HOST:ADMIN_HOST_PORT | |||
| ``` | |||
| ## 数据导入 | |||
| MySQL 导入示例: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec -T mysql \ | |||
| mysql -uroot -p"$MYSQL_ROOT_PASSWORD" emp < emp.sql | |||
| ``` | |||
| TDengine 导入建议仍使用 `taosdump`: | |||
| ```bash | |||
| docker cp td_dump_dir emp-test-tdengine-1:/tmp/td_dump | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec tdengine \ | |||
| taosdump -u root -p"$TDENGINE_PWD" -i /tmp/td_dump | |||
| ``` | |||
| ## 常用命令 | |||
| 查看状态: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps | |||
| ``` | |||
| 看日志: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test logs -f emp-gateway | |||
| ``` | |||
| 停止: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test down | |||
| ``` | |||
| 停止并删除数据卷: | |||
| ```bash | |||
| docker compose --env-file .env -f docker-compose.yml -p emp-test down -v | |||
| ``` | |||
| @@ -0,0 +1,205 @@ | |||
| param( | |||
| [string]$ImageNamespace = $(if ($env:IMAGE_NAMESPACE) { $env:IMAGE_NAMESPACE } else { "emp-test" }), | |||
| [string]$ImageTag = $(if ($env:IMAGE_TAG) { $env:IMAGE_TAG } else { Get-Date -Format "yyyyMMddHHmmss" }), | |||
| [string]$NpmRegistry = $(if ($env:NPM_REGISTRY) { $env:NPM_REGISTRY } else { "https://registry.npmjs.org" }), | |||
| [switch]$SkipBuild, | |||
| [switch]$NoMiddlewareImages | |||
| ) | |||
| $ErrorActionPreference = "Stop" | |||
| # Local package script. Build images locally, export them, then run on server only with docker load + compose up. | |||
| $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path | |||
| $RootDir = Resolve-Path (Join-Path $ScriptDir "..\..") | |||
| $DistDir = Join-Path $ScriptDir "dist" | |||
| $PackageName = "emp-test-runtime-$ImageTag" | |||
| $PackageDir = Join-Path $DistDir $PackageName | |||
| $PackageArchive = Join-Path $DistDir "$PackageName.tar.gz" | |||
| $JavaModules = @("emp_gateway", "emp_auth", "emp_monitor", "emp_data") | |||
| $AppImages = @( | |||
| "$ImageNamespace/emp-gateway:$ImageTag", | |||
| "$ImageNamespace/emp-gateway:latest", | |||
| "$ImageNamespace/emp-auth:$ImageTag", | |||
| "$ImageNamespace/emp-auth:latest", | |||
| "$ImageNamespace/emp-monitor:$ImageTag", | |||
| "$ImageNamespace/emp-monitor:latest", | |||
| "$ImageNamespace/emp-data:$ImageTag", | |||
| "$ImageNamespace/emp-data:latest", | |||
| "$ImageNamespace/emp-pdf:$ImageTag", | |||
| "$ImageNamespace/emp-pdf:latest", | |||
| "$ImageNamespace/emp-ws:$ImageTag", | |||
| "$ImageNamespace/emp-ws:latest", | |||
| "$ImageNamespace/emp-admin:$ImageTag", | |||
| "$ImageNamespace/emp-admin:latest" | |||
| ) | |||
| $MiddlewareImages = @( | |||
| $(if ($env:MYSQL_IMAGE) { $env:MYSQL_IMAGE } else { "mysql:8.0" }), | |||
| $(if ($env:REDIS_IMAGE) { $env:REDIS_IMAGE } else { "redis:7-alpine" }), | |||
| $(if ($env:KAFKA_IMAGE) { $env:KAFKA_IMAGE } else { "bitnami/kafka:3.7.0" }), | |||
| $(if ($env:TDENGINE_IMAGE) { $env:TDENGINE_IMAGE } else { "tdengine/tdengine:3.3.6.0" }), | |||
| $(if ($env:NACOS_IMAGE) { $env:NACOS_IMAGE } else { "nacos/nacos-server:v2.3.2-slim" }) | |||
| ) | |||
| function Write-Log { | |||
| param([string]$Message) | |||
| Write-Host "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" | |||
| } | |||
| function Add-KnownToolPaths { | |||
| $KnownDirs = @( | |||
| "C:\Program Files\Docker\Docker\resources\bin", | |||
| "C:\Program Files\Docker\Docker", | |||
| "$env:ProgramFiles\Docker\Docker\resources\bin" | |||
| ) | |||
| foreach ($Dir in $KnownDirs) { | |||
| if ($Dir -and (Test-Path $Dir) -and (($env:Path -split ';') -notcontains $Dir)) { | |||
| $env:Path = "$Dir;$env:Path" | |||
| } | |||
| } | |||
| } | |||
| function Assert-Command { | |||
| param([string]$Name) | |||
| if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { | |||
| throw "Missing command: $Name" | |||
| } | |||
| } | |||
| function Assert-DockerDaemon { | |||
| & docker info *> $null | |||
| if ($LASTEXITCODE -ne 0) { | |||
| throw "Docker daemon is not running. Start Docker Desktop and wait until it is ready, then rerun this script." | |||
| } | |||
| } | |||
| function Invoke-Step { | |||
| param( | |||
| [string]$WorkingDirectory, | |||
| [string]$FilePath, | |||
| [string[]]$Arguments | |||
| ) | |||
| Push-Location $WorkingDirectory | |||
| try { | |||
| & $FilePath @Arguments | |||
| if ($LASTEXITCODE -ne 0) { | |||
| throw "$FilePath failed, exit code: $LASTEXITCODE" | |||
| } | |||
| } finally { | |||
| Pop-Location | |||
| } | |||
| } | |||
| function Build-JavaImages { | |||
| $ServerDir = Join-Path $RootDir "emp_server" | |||
| Write-Log "Build backend jars" | |||
| Invoke-Step $ServerDir "mvn" @("package", "-DskipTests", "-B", "-pl", ($JavaModules -join ","), "-am") | |||
| foreach ($Module in $JavaModules) { | |||
| $TargetDir = Join-Path $ServerDir "$Module\target" | |||
| $Jar = Get-ChildItem $TargetDir -Filter "*.jar" | | |||
| Where-Object { $_.Name -ne "app.jar" -and $_.Name -notlike "*original*" } | | |||
| Select-Object -First 1 | |||
| if (-not $Jar) { | |||
| throw "Jar not found for $Module" | |||
| } | |||
| Copy-Item $Jar.FullName (Join-Path $TargetDir "app.jar") -Force | |||
| } | |||
| Write-Log "Build backend images" | |||
| Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_gateway", "-t", "$ImageNamespace/emp-gateway:$ImageTag", "-t", "$ImageNamespace/emp-gateway:latest", ".") | |||
| Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_auth", "-t", "$ImageNamespace/emp-auth:$ImageTag", "-t", "$ImageNamespace/emp-auth:latest", ".") | |||
| Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_monitor", "-t", "$ImageNamespace/emp-monitor:$ImageTag", "-t", "$ImageNamespace/emp-monitor:latest", ".") | |||
| Invoke-Step $ServerDir "docker" @("build", "-f", "Dockerfile.service", "--build-arg", "MODULE=emp_data", "-t", "$ImageNamespace/emp-data:$ImageTag", "-t", "$ImageNamespace/emp-data:latest", ".") | |||
| Write-Log "Build PDF image" | |||
| Invoke-Step $ServerDir "docker" @("build", "-f", "emp_pdf/Dockerfile", "-t", "$ImageNamespace/emp-pdf:$ImageTag", "-t", "$ImageNamespace/emp-pdf:latest", "emp_pdf") | |||
| } | |||
| function Build-WsImage { | |||
| Write-Log "Build WS/simulator image" | |||
| Invoke-Step $RootDir "docker" @( | |||
| "build", | |||
| "-f", "deploy/isolated/dockerfiles/emp-ws.Dockerfile", | |||
| "--build-arg", "NPM_REGISTRY=$NpmRegistry", | |||
| "-t", "$ImageNamespace/emp-ws:$ImageTag", | |||
| "-t", "$ImageNamespace/emp-ws:latest", | |||
| "." | |||
| ) | |||
| } | |||
| function Build-AdminImage { | |||
| $AdminDir = Join-Path $RootDir "emp_admin" | |||
| Write-Log "Build frontend dist" | |||
| Invoke-Step $AdminDir "pnpm" @("install", "--frozen-lockfile") | |||
| Invoke-Step $AdminDir "pnpm" @("run", "build:shunfeng") | |||
| Write-Log "Build frontend image" | |||
| Invoke-Step $RootDir "docker" @( | |||
| "build", | |||
| "-f", "deploy/isolated/dockerfiles/emp-admin.Dockerfile", | |||
| "-t", "$ImageNamespace/emp-admin:$ImageTag", | |||
| "-t", "$ImageNamespace/emp-admin:latest", | |||
| "." | |||
| ) | |||
| } | |||
| function Prepare-Package { | |||
| if (Test-Path $PackageDir) { | |||
| Remove-Item $PackageDir -Recurse -Force | |||
| } | |||
| New-Item -ItemType Directory -Force $PackageDir | Out-Null | |||
| Copy-Item (Join-Path $ScriptDir "docker-compose.runtime.yml") (Join-Path $PackageDir "docker-compose.yml") -Force | |||
| Copy-Item (Join-Path $ScriptDir ".env.example") (Join-Path $PackageDir ".env.example") -Force | |||
| Copy-Item (Join-Path $ScriptDir "install.sh") (Join-Path $PackageDir "install.sh") -Force | |||
| Copy-Item (Join-Path $ScriptDir "README.md") (Join-Path $PackageDir "README.md") -Force | |||
| } | |||
| function Save-Images { | |||
| $Images = New-Object System.Collections.Generic.List[string] | |||
| foreach ($Image in $AppImages) { | |||
| $Images.Add($Image) | |||
| } | |||
| if (-not $NoMiddlewareImages) { | |||
| Write-Log "Pull middleware images" | |||
| foreach ($Image in $MiddlewareImages) { | |||
| Invoke-Step $RootDir "docker" @("pull", $Image) | |||
| $Images.Add($Image) | |||
| } | |||
| } | |||
| Write-Log "Save images" | |||
| Invoke-Step $RootDir "docker" @(@("save", "-o", (Join-Path $PackageDir "images.tar")) + $Images.ToArray()) | |||
| } | |||
| function Archive-Package { | |||
| New-Item -ItemType Directory -Force $DistDir | Out-Null | |||
| if (Test-Path $PackageArchive) { | |||
| Remove-Item $PackageArchive -Force | |||
| } | |||
| Invoke-Step $DistDir "tar" @("-czf", $PackageArchive, $PackageName) | |||
| Write-Log "Package created: $PackageArchive" | |||
| } | |||
| Add-KnownToolPaths | |||
| Assert-Command "docker" | |||
| Assert-DockerDaemon | |||
| Assert-Command "tar" | |||
| if (-not $SkipBuild) { | |||
| Assert-Command "mvn" | |||
| Assert-Command "pnpm" | |||
| Build-JavaImages | |||
| Build-WsImage | |||
| Build-AdminImage | |||
| } else { | |||
| Write-Log "Skip build, package existing local images only" | |||
| } | |||
| Prepare-Package | |||
| Save-Images | |||
| Archive-Package | |||
| @@ -0,0 +1,154 @@ | |||
| #!/usr/bin/env bash | |||
| set -Eeuo pipefail | |||
| # 本地打包脚本:本机完成构建和 docker save,服务器只需要 docker load + compose up。 | |||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||
| ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" | |||
| DIST_DIR="$SCRIPT_DIR/dist" | |||
| IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-emp-test}" | |||
| IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}" | |||
| PACKAGE_NAME="${PACKAGE_NAME:-emp-test-runtime-${IMAGE_TAG}}" | |||
| PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME" | |||
| PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz" | |||
| NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" | |||
| SKIP_BUILD="${SKIP_BUILD:-0}" | |||
| INCLUDE_MIDDLEWARE_IMAGES="${INCLUDE_MIDDLEWARE_IMAGES:-1}" | |||
| JAVA_MODULES=(emp_gateway emp_auth emp_monitor emp_data) | |||
| APP_IMAGES=( | |||
| "$IMAGE_NAMESPACE/emp-gateway:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-gateway:latest" | |||
| "$IMAGE_NAMESPACE/emp-auth:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-auth:latest" | |||
| "$IMAGE_NAMESPACE/emp-monitor:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-monitor:latest" | |||
| "$IMAGE_NAMESPACE/emp-data:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-data:latest" | |||
| "$IMAGE_NAMESPACE/emp-pdf:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-pdf:latest" | |||
| "$IMAGE_NAMESPACE/emp-ws:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-ws:latest" | |||
| "$IMAGE_NAMESPACE/emp-admin:$IMAGE_TAG" | |||
| "$IMAGE_NAMESPACE/emp-admin:latest" | |||
| ) | |||
| MIDDLEWARE_IMAGES=( | |||
| "${MYSQL_IMAGE:-mysql:8.0}" | |||
| "${REDIS_IMAGE:-redis:7-alpine}" | |||
| "${KAFKA_IMAGE:-bitnami/kafka:3.7.0}" | |||
| "${TDENGINE_IMAGE:-tdengine/tdengine:3.3.6.0}" | |||
| "${NACOS_IMAGE:-nacos/nacos-server:v2.3.2-slim}" | |||
| ) | |||
| log() { | |||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |||
| } | |||
| die() { | |||
| echo "ERROR: $*" >&2 | |||
| exit 1 | |||
| } | |||
| need_cmd() { | |||
| command -v "$1" >/dev/null 2>&1 || die "缺少命令:$1" | |||
| } | |||
| build_java_images() { | |||
| cd "$ROOT_DIR/emp_server" | |||
| log "构建后端 jar" | |||
| mvn package -DskipTests -B -pl "$(IFS=,; echo "${JAVA_MODULES[*]}")" -am | |||
| for module in "${JAVA_MODULES[@]}"; do | |||
| local jar | |||
| jar="$(ls "$module"/target/*.jar | grep -v original | grep -v app.jar | head -1)" | |||
| cp "$jar" "$module/target/app.jar" | |||
| done | |||
| log "构建后端镜像" | |||
| docker build -f Dockerfile.service --build-arg MODULE=emp_gateway -t "$IMAGE_NAMESPACE/emp-gateway:$IMAGE_TAG" -t "$IMAGE_NAMESPACE/emp-gateway:latest" . | |||
| docker build -f Dockerfile.service --build-arg MODULE=emp_auth -t "$IMAGE_NAMESPACE/emp-auth:$IMAGE_TAG" -t "$IMAGE_NAMESPACE/emp-auth:latest" . | |||
| docker build -f Dockerfile.service --build-arg MODULE=emp_monitor -t "$IMAGE_NAMESPACE/emp-monitor:$IMAGE_TAG" -t "$IMAGE_NAMESPACE/emp-monitor:latest" . | |||
| docker build -f Dockerfile.service --build-arg MODULE=emp_data -t "$IMAGE_NAMESPACE/emp-data:$IMAGE_TAG" -t "$IMAGE_NAMESPACE/emp-data:latest" . | |||
| log "构建 PDF 镜像" | |||
| docker build -f emp_pdf/Dockerfile -t "$IMAGE_NAMESPACE/emp-pdf:$IMAGE_TAG" -t "$IMAGE_NAMESPACE/emp-pdf:latest" emp_pdf | |||
| } | |||
| build_ws_image() { | |||
| cd "$ROOT_DIR" | |||
| log "构建 WS/模拟器镜像" | |||
| docker build \ | |||
| -f deploy/isolated/dockerfiles/emp-ws.Dockerfile \ | |||
| --build-arg "NPM_REGISTRY=$NPM_REGISTRY" \ | |||
| -t "$IMAGE_NAMESPACE/emp-ws:$IMAGE_TAG" \ | |||
| -t "$IMAGE_NAMESPACE/emp-ws:latest" \ | |||
| . | |||
| } | |||
| build_admin_image() { | |||
| cd "$ROOT_DIR/emp_admin" | |||
| log "构建前端 dist" | |||
| pnpm install --frozen-lockfile | |||
| pnpm run build:shunfeng | |||
| cd "$ROOT_DIR" | |||
| log "构建前端镜像" | |||
| docker build \ | |||
| -f deploy/isolated/dockerfiles/emp-admin.Dockerfile \ | |||
| -t "$IMAGE_NAMESPACE/emp-admin:$IMAGE_TAG" \ | |||
| -t "$IMAGE_NAMESPACE/emp-admin:latest" \ | |||
| . | |||
| } | |||
| prepare_package() { | |||
| rm -rf "$PACKAGE_DIR" | |||
| mkdir -p "$PACKAGE_DIR" | |||
| cp "$SCRIPT_DIR/docker-compose.runtime.yml" "$PACKAGE_DIR/docker-compose.yml" | |||
| cp "$SCRIPT_DIR/.env.example" "$PACKAGE_DIR/.env.example" | |||
| cp "$SCRIPT_DIR/install.sh" "$PACKAGE_DIR/install.sh" | |||
| cp "$SCRIPT_DIR/README.md" "$PACKAGE_DIR/README.md" | |||
| chmod +x "$PACKAGE_DIR/install.sh" | |||
| } | |||
| save_images() { | |||
| local images=("${APP_IMAGES[@]}") | |||
| if [[ "$INCLUDE_MIDDLEWARE_IMAGES" == "1" ]]; then | |||
| log "拉取中间件镜像" | |||
| for image in "${MIDDLEWARE_IMAGES[@]}"; do | |||
| docker pull "$image" | |||
| images+=("$image") | |||
| done | |||
| fi | |||
| log "导出镜像包" | |||
| docker save -o "$PACKAGE_DIR/images.tar" "${images[@]}" | |||
| } | |||
| archive_package() { | |||
| mkdir -p "$DIST_DIR" | |||
| rm -f "$PACKAGE_ARCHIVE" | |||
| tar -czf "$PACKAGE_ARCHIVE" -C "$DIST_DIR" "$PACKAGE_NAME" | |||
| log "打包完成:$PACKAGE_ARCHIVE" | |||
| } | |||
| need_cmd docker | |||
| need_cmd tar | |||
| if [[ "$SKIP_BUILD" != "1" ]]; then | |||
| need_cmd mvn | |||
| need_cmd pnpm | |||
| build_java_images | |||
| build_ws_image | |||
| build_admin_image | |||
| else | |||
| log "跳过构建,仅打包当前本机已有镜像" | |||
| fi | |||
| prepare_package | |||
| save_images | |||
| archive_package | |||
| @@ -0,0 +1,247 @@ | |||
| x-app-env: &app-env | |||
| env_file: | |||
| - .env | |||
| restart: unless-stopped | |||
| networks: | |||
| - emp-net | |||
| x-java-depends: &java-depends | |||
| nacos: | |||
| condition: service_healthy | |||
| mysql: | |||
| condition: service_healthy | |||
| redis: | |||
| condition: service_healthy | |||
| services: | |||
| mysql: | |||
| image: ${MYSQL_IMAGE:-mysql:8.0} | |||
| restart: unless-stopped | |||
| ports: | |||
| - "0.0.0.0:${MYSQL_HOST_PORT:-13306}:3306" | |||
| environment: | |||
| TZ: Asia/Shanghai | |||
| MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} | |||
| MYSQL_DATABASE: ${MYSQL_DATABASE:-emp} | |||
| command: | |||
| - --character-set-server=utf8mb4 | |||
| - --collation-server=utf8mb4_0900_ai_ci | |||
| - --default-time-zone=+08:00 | |||
| - --max-connections=1000 | |||
| volumes: | |||
| - mysql_data:/var/lib/mysql | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$${MYSQL_ROOT_PASSWORD}\" --silent"] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| networks: | |||
| - emp-net | |||
| redis: | |||
| image: ${REDIS_IMAGE:-redis:7-alpine} | |||
| restart: unless-stopped | |||
| ports: | |||
| - "${REDIS_BIND_HOST:-127.0.0.1}:${REDIS_HOST_PORT:-16379}:6379" | |||
| command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}"] | |||
| volumes: | |||
| - redis_data:/data | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep -q PONG"] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| networks: | |||
| - emp-net | |||
| kafka: | |||
| image: ${KAFKA_IMAGE:-bitnami/kafka:3.7.0} | |||
| restart: unless-stopped | |||
| ports: | |||
| - "0.0.0.0:${KAFKA_HOST_PORT:-19094}:9094" | |||
| environment: | |||
| ALLOW_PLAINTEXT_LISTENER: "yes" | |||
| KAFKA_CFG_NODE_ID: 1 | |||
| KAFKA_CFG_PROCESS_ROLES: controller,broker | |||
| KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 | |||
| KAFKA_CFG_LISTENERS: INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:9094 | |||
| KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,EXTERNAL://${PUBLIC_HOST}:${KAFKA_HOST_PORT:-19094} | |||
| KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT | |||
| KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL | |||
| KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER | |||
| KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true" | |||
| KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 | |||
| KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 | |||
| KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: 1 | |||
| volumes: | |||
| - kafka_data:/bitnami/kafka | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list >/dev/null 2>&1"] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| networks: | |||
| - emp-net | |||
| kafka-init: | |||
| image: ${KAFKA_IMAGE:-bitnami/kafka:3.7.0} | |||
| restart: "no" | |||
| depends_on: | |||
| kafka: | |||
| condition: service_healthy | |||
| entrypoint: ["/bin/bash", "-lc"] | |||
| command: > | |||
| /opt/bitnami/kafka/bin/kafka-topics.sh | |||
| --bootstrap-server kafka:9092 | |||
| --create | |||
| --if-not-exists | |||
| --topic ${KAFKA_TOPIC:-vehicle-data} | |||
| --partitions 3 | |||
| --replication-factor 1 | |||
| networks: | |||
| - emp-net | |||
| tdengine: | |||
| image: ${TDENGINE_IMAGE:-tdengine/tdengine:3.3.6.0} | |||
| hostname: tdengine | |||
| privileged: true | |||
| restart: unless-stopped | |||
| ports: | |||
| - "0.0.0.0:${TDENGINE_HOST_PORT:-6030}:6030" | |||
| - "0.0.0.0:${TDENGINE_REST_HOST_PORT:-6041}:6041" | |||
| - "0.0.0.0:${TDENGINE_RPC_HOST_PORT:-6043}:6043" | |||
| - "0.0.0.0:${TDENGINE_RPC_UDP_HOST_PORT:-6044}:6044/udp" | |||
| - "0.0.0.0:${TDENGINE_KEEPER_HOST_PORT:-6060}:6060" | |||
| environment: | |||
| TZ: Asia/Shanghai | |||
| TAOS_FQDN: ${PUBLIC_HOST} | |||
| volumes: | |||
| - tdengine_data:/var/lib/taos | |||
| - tdengine_log:/var/log/taos | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "taos -s 'show databases;' >/dev/null 2>&1"] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| networks: | |||
| - emp-net | |||
| tdengine-init: | |||
| image: ${TDENGINE_IMAGE:-tdengine/tdengine:3.3.6.0} | |||
| restart: "no" | |||
| depends_on: | |||
| tdengine: | |||
| condition: service_healthy | |||
| entrypoint: ["/bin/sh", "-lc"] | |||
| command: > | |||
| taos -h tdengine -u ${TDENGINE_USER:-root} -p"${TDENGINE_PWD:-taosdata}" | |||
| -s "create database if not exists ${TDENGINE_DATABASE:-emp};" | |||
| networks: | |||
| - emp-net | |||
| nacos: | |||
| image: ${NACOS_IMAGE:-nacos/nacos-server:v2.3.2-slim} | |||
| restart: unless-stopped | |||
| ports: | |||
| - "0.0.0.0:${NACOS_HOST_PORT:-9008}:8848" | |||
| - "0.0.0.0:${NACOS_GRPC_HOST_PORT:-10008}:9848" | |||
| environment: | |||
| MODE: standalone | |||
| JVM_XMS: 256m | |||
| JVM_XMX: 512m | |||
| SPRING_DATASOURCE_PLATFORM: "" | |||
| NACOS_AUTH_ENABLE: ${NACOS_AUTH_ENABLE:-true} | |||
| NACOS_AUTH_IDENTITY_KEY: ${NACOS_AUTH_IDENTITY_KEY:-emp} | |||
| NACOS_AUTH_IDENTITY_VALUE: ${NACOS_AUTH_IDENTITY_VALUE:-emp2026} | |||
| NACOS_AUTH_TOKEN: ${NACOS_AUTH_TOKEN} | |||
| volumes: | |||
| - nacos_data:/home/nacos/data | |||
| - nacos_logs:/home/nacos/logs | |||
| healthcheck: | |||
| test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8848/nacos/actuator/health || exit 1"] | |||
| interval: 10s | |||
| timeout: 5s | |||
| retries: 30 | |||
| networks: | |||
| - emp-net | |||
| emp-gateway: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-gateway:${IMAGE_TAG:-latest} | |||
| ports: | |||
| - "0.0.0.0:${GATEWAY_HOST_PORT:-9000}:9000" | |||
| depends_on: | |||
| <<: *java-depends | |||
| emp-auth: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-auth:${IMAGE_TAG:-latest} | |||
| depends_on: | |||
| <<: *java-depends | |||
| emp-monitor: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-monitor:${IMAGE_TAG:-latest} | |||
| depends_on: | |||
| <<: *java-depends | |||
| tdengine: | |||
| condition: service_healthy | |||
| emp-pdf: | |||
| condition: service_started | |||
| emp-data: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-data:${IMAGE_TAG:-latest} | |||
| depends_on: | |||
| <<: *java-depends | |||
| tdengine: | |||
| condition: service_healthy | |||
| kafka-init: | |||
| condition: service_completed_successfully | |||
| emp-ws: | |||
| condition: service_started | |||
| emp-pdf: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | |||
| ports: | |||
| - "127.0.0.1:${PDF_HOST_PORT:-3100}:3100" | |||
| emp-ws: | |||
| <<: *app-env | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-ws:${IMAGE_TAG:-latest} | |||
| ports: | |||
| - "0.0.0.0:${WS_HOST_PORT:-3000}:3000" | |||
| depends_on: | |||
| mysql: | |||
| condition: service_healthy | |||
| redis: | |||
| condition: service_healthy | |||
| kafka-init: | |||
| condition: service_completed_successfully | |||
| emp-admin: | |||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-admin:${IMAGE_TAG:-latest} | |||
| restart: unless-stopped | |||
| ports: | |||
| - "0.0.0.0:${ADMIN_HOST_PORT:-4081}:80" | |||
| depends_on: | |||
| emp-gateway: | |||
| condition: service_started | |||
| emp-ws: | |||
| condition: service_started | |||
| networks: | |||
| - emp-net | |||
| networks: | |||
| emp-net: | |||
| driver: bridge | |||
| volumes: | |||
| mysql_data: | |||
| redis_data: | |||
| kafka_data: | |||
| tdengine_data: | |||
| tdengine_log: | |||
| nacos_data: | |||
| nacos_logs: | |||
| @@ -0,0 +1,7 @@ | |||
| FROM nginx:alpine | |||
| COPY emp_admin/dist /usr/share/nginx/html | |||
| COPY deploy/isolated/nginx/admin.conf /etc/nginx/conf.d/default.conf | |||
| EXPOSE 80 | |||
| @@ -0,0 +1,17 @@ | |||
| FROM node:20-alpine | |||
| ARG NPM_REGISTRY=https://registry.npmjs.org | |||
| WORKDIR /app | |||
| ENV NODE_ENV=production | |||
| COPY emp_ws/package*.json ./ | |||
| RUN npm config set registry "$NPM_REGISTRY" \ | |||
| && npm install --omit=dev | |||
| COPY emp_ws ./ | |||
| RUN mkdir -p runtime | |||
| EXPOSE 3000 | |||
| CMD ["node", "server.js"] | |||
| @@ -0,0 +1,67 @@ | |||
| #!/usr/bin/env bash | |||
| set -Eeuo pipefail | |||
| # 服务器运行脚本:只加载本地镜像包并启动 compose,不做源码构建。 | |||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||
| cd "$SCRIPT_DIR" | |||
| PROJECT_NAME="${PROJECT_NAME:-emp-test}" | |||
| ENV_FILE="${ENV_FILE:-.env}" | |||
| COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" | |||
| IMAGE_TAR="${IMAGE_TAR:-images.tar}" | |||
| log() { | |||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |||
| } | |||
| die() { | |||
| echo "ERROR: $*" >&2 | |||
| exit 1 | |||
| } | |||
| need_cmd() { | |||
| command -v "$1" >/dev/null 2>&1 || die "缺少命令:$1" | |||
| } | |||
| need_cmd docker | |||
| if docker compose version >/dev/null 2>&1; then | |||
| DC=(docker compose) | |||
| elif command -v docker-compose >/dev/null 2>&1; then | |||
| DC=(docker-compose) | |||
| else | |||
| die "未安装 docker compose" | |||
| fi | |||
| if [[ ! -f "$ENV_FILE" ]]; then | |||
| cp .env.example "$ENV_FILE" | |||
| die "已生成 $ENV_FILE,请先修改密码、PUBLIC_HOST 和端口后重新执行:sh install.sh" | |||
| fi | |||
| if [[ ! -f "$COMPOSE_FILE" ]]; then | |||
| die "未找到 $COMPOSE_FILE" | |||
| fi | |||
| if [[ -f "$IMAGE_TAR" ]]; then | |||
| log "加载镜像包:$IMAGE_TAR" | |||
| docker load -i "$IMAGE_TAR" | |||
| else | |||
| log "未找到 $IMAGE_TAR,将尝试使用本机已有镜像或在线拉取镜像" | |||
| fi | |||
| log "启动隔离测试环境:$PROJECT_NAME" | |||
| "${DC[@]}" \ | |||
| --env-file "$ENV_FILE" \ | |||
| -f "$COMPOSE_FILE" \ | |||
| -p "$PROJECT_NAME" \ | |||
| up -d | |||
| log "当前容器状态" | |||
| "${DC[@]}" \ | |||
| --env-file "$ENV_FILE" \ | |||
| -f "$COMPOSE_FILE" \ | |||
| -p "$PROJECT_NAME" \ | |||
| ps | |||
| log "完成。前端地址请访问 .env 中的 PUBLIC_HOST:ADMIN_HOST_PORT。" | |||
| @@ -0,0 +1,53 @@ | |||
| server { | |||
| listen 80; | |||
| server_name _; | |||
| root /usr/share/nginx/html; | |||
| index index.html; | |||
| client_max_body_size 105m; | |||
| gzip on; | |||
| gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml; | |||
| gzip_min_length 1024; | |||
| location = /index.html { | |||
| add_header Cache-Control "no-store"; | |||
| } | |||
| location /api/ { | |||
| proxy_pass http://emp-gateway:9000/api/; | |||
| proxy_http_version 1.1; | |||
| proxy_set_header Host $host; | |||
| proxy_set_header X-Real-IP $remote_addr; | |||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||
| proxy_set_header X-Forwarded-Proto $scheme; | |||
| proxy_read_timeout 300s; | |||
| } | |||
| location /socket.io/ { | |||
| proxy_pass http://emp-ws:3000/socket.io/; | |||
| proxy_http_version 1.1; | |||
| proxy_set_header Upgrade $http_upgrade; | |||
| proxy_set_header Connection "upgrade"; | |||
| proxy_set_header Host $host; | |||
| proxy_read_timeout 300s; | |||
| } | |||
| location / { | |||
| add_header Cache-Control "no-store"; | |||
| try_files $uri $uri/ /index.html; | |||
| } | |||
| location ~* \.(js|css)$ { | |||
| expires -1; | |||
| add_header Cache-Control "no-cache, no-store, must-revalidate"; | |||
| try_files $uri =404; | |||
| } | |||
| location ~* \.(png|jpg|jpeg|gif|ico|svg|woff2?|ttf|glb)$ { | |||
| expires 30d; | |||
| add_header Cache-Control "public, immutable"; | |||
| try_files $uri =404; | |||
| } | |||
| } | |||