diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e7e0a13 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +target/ +.m2/ +*.log +*.tmp +.DS_Store +.idea/ +*.iml +.vscode/ +node_modules/ +dist/ +build/ +*.class +*.jar +!gradle-wrapper.jar +.gradle/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d2663d7 --- /dev/null +++ b/.env.example @@ -0,0 +1,126 @@ +# ============================================ +# 环境变量配置文件 +# 复制此文件为 .env 并根据实际环境修改 +# ============================================ + +# ============ 通用配置 ============ +COMPOSE_PROJECT_NAME=aiot-platform +TZ=Asia/Shanghai + +# ============ Docker Registry ============ +REGISTRY_HOST=localhost:5000 +IMAGE_TAG=latest + +# ============ 数据库配置 ============ +# 1Panel 安装的 MySQL 配置 +MYSQL_HOST=127.0.0.1 +MYSQL_PORT=3306 +MYSQL_ROOT_PASSWORD=your_1panel_mysql_root_password +MYSQL_DATABASE=aiot_platform +MYSQL_USER=aiot +MYSQL_PASSWORD=aiot_password + +# MySQL 内存配置(1Panel 管理,此处配置不生效) +MYSQL_MEMORY_LIMIT=2048m +MYSQL_MEMORY_RESERVATION=1024m + +# ============ Redis 配置 ============ +# 1Panel 安装的 Redis 配置 +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 +REDIS_PASSWORD=your_1panel_redis_password + +# Redis 内存配置(1Panel 管理,此处配置不生效) +REDIS_MEMORY_LIMIT=512m +REDIS_MEMORY_RESERVATION=256m + +# ============ Nacos 配置 ============ +# 1Panel 安装的 Nacos 配置 +NACOS_HOST=127.0.0.1 +NACOS_PORT=8848 +NACOS_NAMESPACE=aiot-platform +NACOS_USERNAME=nacos +NACOS_PASSWORD=nacos + +# Nacos 内存配置(1Panel 管理,此处配置不生效) +NACOS_MEMORY_LIMIT=1024m +NACOS_MEMORY_RESERVATION=768m + +# ============ RocketMQ 配置 ============ +# 1Panel 安装的 RocketMQ 配置 +ROCKETMQ_NAMESRV_HOST=127.0.0.1 +ROCKETMQ_NAMESRV_PORT=9876 +ROCKETMQ_BROKER_HOST=127.0.0.1 + +# RocketMQ 内存配置(1Panel 管理,此处配置不生效) +ROCKETMQ_NAMESRV_MEMORY_LIMIT=512m +ROCKETMQ_BROKER_MEMORY_LIMIT=1536m + +# ============ 应用服务内存配置 ============ +# 格式: 服务名_MEMORY_LIMIT / 服务名_JVM_XMS / 服务名_JVM_XMX + +# API 网关(标准配置) +GATEWAY_MEMORY_LIMIT=768m +GATEWAY_JVM_XMS=512m +GATEWAY_JVM_XMX=768m + +# 系统服务(标准配置) +SYSTEM_MEMORY_LIMIT=768m +SYSTEM_JVM_XMS=512m +SYSTEM_JVM_XMX=768m + +# 基础设施服务(标准配置) +INFRA_MEMORY_LIMIT=768m +INFRA_JVM_XMS=512m +INFRA_JVM_XMX=768m + +# IoT 业务服务(核心服务,较大内存) +IOT_SERVER_MEMORY_LIMIT=1024m +IOT_SERVER_JVM_XMS=768m +IOT_SERVER_JVM_XMX=1024m + +# IoT 设备网关(核心服务,较大内存) +IOT_GATEWAY_MEMORY_LIMIT=1024m +IOT_GATEWAY_JVM_XMS=768m +IOT_GATEWAY_JVM_XMX=1024m + +# 运维服务(轻量配置) +OPS_MEMORY_LIMIT=384m +OPS_JVM_XMS=256m +OPS_JVM_XMX=384m + +# ============ JVM 通用参数 ============ +JVM_COMMON_OPTS=-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError + +# ============ Spring Boot 配置 ============ +SPRING_PROFILES_ACTIVE=prod + +# ============ 日志配置 ============ +LOG_LEVEL=INFO +LOG_PATH=/app/logs + +# ============ 端口映射 ============ +GATEWAY_PORT=48080 +SYSTEM_PORT=48081 +INFRA_PORT=48082 +IOT_SERVER_PORT=48083 +IOT_GATEWAY_PORT=48084 +OPS_PORT=48085 + +# ============ 健康检查配置 ============ +HEALTH_CHECK_INTERVAL=30s +HEALTH_CHECK_TIMEOUT=10s +HEALTH_CHECK_RETRIES=3 +HEALTH_CHECK_START_PERIOD=60s + +# ============ 资源限制说明 ============ +# 当前配置预计总内存占用: +# - 应用服务: ~4.5GB (Docker 容器) +# - 中间件: 由 1Panel 管理(不在 Docker Compose 中) +# - 总计: ~4.5GB (仅应用服务,为系统预留 11.5GB) +# +# 调整建议: +# 1. 核心服务(IOT_SERVER, IOT_GATEWAY)已配置较大内存 (1GB) +# 2. 如需增加某服务内存,修改对应的 MEMORY_LIMIT 和 JVM_XMX +# 3. 中间件内存配置请在 1Panel 面板中调整 +# 4. 确保所有服务总内存不超过 14GB,为系统预留至少 2GB diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..76645e3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,282 @@ +// ============================================ +// AIOT Platform - Jenkins Pipeline +// 智能增量构建 + Docker 容器化部署 +// ============================================ + +pipeline { + agent any + + options { + // 保留最近 10 次构建 + buildDiscarder(logRotator(numToKeepStr: '10')) + // 禁止并发构建 + disableConcurrentBuilds() + // 超时设置 + timeout(time: 60, unit: 'MINUTES') + } + + environment { + // Gitea 仓库配置 + GIT_REPO = 'http://124.221.55.225:3000/XW-AIOT/aiot-platform-cloud.git' + + // Docker Registry 配置 + REGISTRY = 'localhost:5000' + + // Maven 配置 + MAVEN_OPTS = '-Xmx2048m -Dmaven.repo.local=.m2/repository' + + // 镜像标签 + IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}-${env.GIT_COMMIT?.take(8)}" + + // 服务列表(核心服务) + CORE_SERVICES = 'viewsh-gateway,viewsh-module-system-server,viewsh-module-infra-server,viewsh-module-iot-server,viewsh-module-iot-gateway,viewsh-module-ops-server' + } + + stages { + stage('Checkout') { + steps { + script { + echo "=== 检出代码 ===" + checkout scm + + // 获取 Git 提交信息 + env.GIT_COMMIT_MSG = sh( + script: 'git log -1 --pretty=%B', + returnStdout: true + ).trim() + + echo "Commit: ${env.GIT_COMMIT}" + echo "Message: ${env.GIT_COMMIT_MSG}" + } + } + } + + stage('Detect Changes') { + steps { + script { + echo "=== 检测变更的服务 ===" + + // 获取变更的文件列表 + def changedFiles = sh( + script: ''' + if [ "${GIT_PREVIOUS_COMMIT}" = "" ]; then + # 首次构建,构建所有核心服务 + echo "all" + else + # 检测变更的文件 + git diff --name-only ${GIT_PREVIOUS_COMMIT} ${GIT_COMMIT} + fi + ''', + returnStdout: true + ).trim() + + echo "Changed files:\n${changedFiles}" + + // 分析需要构建的服务 + def servicesToBuild = [] + + if (changedFiles == 'all') { + // 首次构建或强制全量构建 + servicesToBuild = CORE_SERVICES.split(',') + } else { + // 检测每个服务是否有变更 + CORE_SERVICES.split(',').each { service -> + def modulePath = service.replace('-server', '').replace('viewsh-module-', 'viewsh-module-').replace('viewsh-', '') + + if (changedFiles.contains(modulePath) || + changedFiles.contains('pom.xml') || + changedFiles.contains('viewsh-framework') || + changedFiles.contains('viewsh-dependencies')) { + servicesToBuild.add(service) + } + } + + // 如果没有检测到变更,但有代码提交,构建所有服务 + if (servicesToBuild.isEmpty() && !changedFiles.isEmpty()) { + servicesToBuild = CORE_SERVICES.split(',') + } + } + + env.SERVICES_TO_BUILD = servicesToBuild.join(',') + echo "Services to build: ${env.SERVICES_TO_BUILD}" + + if (servicesToBuild.isEmpty()) { + echo "No services need to be built. Skipping build." + currentBuild.result = 'SUCCESS' + return + } + } + } + } + + stage('Maven Build') { + when { + expression { env.SERVICES_TO_BUILD != '' } + } + steps { + script { + echo "=== Maven 构建 ===" + + def services = env.SERVICES_TO_BUILD.split(',') + + // 并行构建(最多 3 个并行,避免内存溢出) + def parallelBuilds = [:] + def batchSize = 3 + + for (int i = 0; i < services.size(); i += batchSize) { + def batch = services[i..Math.min(i + batchSize - 1, services.size() - 1)] + + batch.each { service -> + def modulePath = getModulePath(service) + + parallelBuilds["Build ${service}"] = { + stage("Build ${service}") { + echo "Building ${service}..." + sh """ + mvn clean package -pl ${modulePath} -am -DskipTests -B + """ + } + } + } + + // 执行当前批次 + parallel parallelBuilds + parallelBuilds = [:] + } + } + } + } + + stage('Docker Build & Push') { + when { + expression { env.SERVICES_TO_BUILD != '' } + } + steps { + script { + echo "=== Docker 镜像构建与推送 ===" + + def services = env.SERVICES_TO_BUILD.split(',') + def parallelBuilds = [:] + + services.each { service -> + parallelBuilds["Docker ${service}"] = { + stage("Docker ${service}") { + def modulePath = getModulePath(service) + def jarName = service + + echo "Building Docker image for ${service}..." + + sh """ + docker build \ + -f docker/Dockerfile.template \ + --build-arg MODULE_NAME=${modulePath} \ + --build-arg JAR_NAME=${jarName} \ + --build-arg SKIP_TESTS=true \ + -t ${REGISTRY}/${service}:${IMAGE_TAG} \ + -t ${REGISTRY}/${service}:latest \ + . + """ + + echo "Pushing Docker image for ${service}..." + sh """ + docker push ${REGISTRY}/${service}:${IMAGE_TAG} + docker push ${REGISTRY}/${service}:latest + """ + + echo "Cleaning up old images for ${service}..." + sh """ + docker images ${REGISTRY}/${service} --format '{{.ID}} {{.Tag}}' | \ + grep -v '${IMAGE_TAG}' | grep -v 'latest' | \ + awk '{print \$1}' | head -n -2 | xargs -r docker rmi -f || true + """ + } + } + } + + // 并行构建镜像(最多 3 个并行) + def batchSize = 3 + for (int i = 0; i < services.size(); i += batchSize) { + def batch = services[i..Math.min(i + batchSize - 1, services.size() - 1)] + def batchBuilds = [:] + + batch.each { service -> + batchBuilds["Docker ${service}"] = parallelBuilds["Docker ${service}"] + } + + parallel batchBuilds + } + } + } + } + + stage('Deploy') { + when { + expression { env.SERVICES_TO_BUILD != '' && env.BRANCH_NAME == 'master' } + } + steps { + script { + echo "=== 部署到生产环境 ===" + + def services = env.SERVICES_TO_BUILD.split(',') + + services.each { service -> + echo "Deploying ${service}..." + + sh """ + docker-compose -f docker-compose.core.yml pull ${service} + docker-compose -f docker-compose.core.yml up -d ${service} + """ + + // 等待服务健康检查 + echo "Waiting for ${service} to be healthy..." + sh """ + timeout 120 sh -c 'until docker inspect --format="{{.State.Health.Status}}" aiot-${service} 2>/dev/null | grep -q healthy; do sleep 5; done' || true + """ + } + + echo "Deployment completed!" + } + } + } + } + + post { + success { + echo "=== 构建成功 ===" + echo "Built services: ${env.SERVICES_TO_BUILD}" + echo "Image tag: ${IMAGE_TAG}" + } + + failure { + echo "=== 构建失败 ===" + echo "Please check the logs for details." + } + + always { + // 清理工作空间(可选) + // cleanWs() + + // 显示磁盘使用情况 + sh 'df -h' + sh 'docker system df' + } + } +} + +// ============================================ +// 辅助函数 +// ============================================ + +def getModulePath(String service) { + // 根据服务名获取 Maven 模块路径 + def moduleMap = [ + 'viewsh-gateway': 'viewsh-gateway', + 'viewsh-module-system-server': 'viewsh-module-system/viewsh-module-system-server', + 'viewsh-module-infra-server': 'viewsh-module-infra/viewsh-module-infra-server', + 'viewsh-module-iot-server': 'viewsh-module-iot/viewsh-module-iot-server', + 'viewsh-module-iot-gateway': 'viewsh-module-iot/viewsh-module-iot-gateway', + 'viewsh-module-ops-server': 'viewsh-module-ops/viewsh-module-ops-server' + ] + + return moduleMap[service] ?: service +} diff --git a/docker-compose.core.yml b/docker-compose.core.yml new file mode 100644 index 0000000..d1e3d1e --- /dev/null +++ b/docker-compose.core.yml @@ -0,0 +1,203 @@ +version: '3.8' + +# ============================================ +# AIOT Platform - 核心服务部署配置 (方案 A) +# 连接到 1Panel 已安装的中间件 +# 总内存占用: ~4.5GB (仅应用服务) +# ============================================ + +networks: + aiot-network: + driver: bridge + +volumes: + app-logs: + +services: + # ============ 应用服务 ============ + # 注意: 中间件(MySQL, Redis, Nacos, RocketMQ)已通过 1Panel 安装 + # 应用服务通过环境变量配置连接到宿主机的中间件 + + + viewsh-gateway: + image: ${REGISTRY_HOST}/viewsh-gateway:${IMAGE_TAG} + container_name: aiot-gateway + restart: unless-stopped + network_mode: host # 使用宿主机网络,直接访问 1Panel 中间件 + environment: + JAVA_OPTS: "-Xms${GATEWAY_JVM_XMS} -Xmx${GATEWAY_JVM_XMX} ${JVM_COMMON_OPTS}" + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + NACOS_SERVER_ADDR: ${NACOS_HOST}:${NACOS_PORT} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_PASSWORD: ${REDIS_PASSWORD} + TZ: ${TZ} + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: ${GATEWAY_MEMORY_LIMIT} + cpus: '1.0' + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:48080/actuator/health"] + interval: ${HEALTH_CHECK_INTERVAL} + timeout: ${HEALTH_CHECK_TIMEOUT} + retries: ${HEALTH_CHECK_RETRIES} + start_period: ${HEALTH_CHECK_START_PERIOD} + + viewsh-module-system-server: + image: ${REGISTRY_HOST}/viewsh-module-system-server:${IMAGE_TAG} + container_name: aiot-system-server + restart: unless-stopped + network_mode: host + environment: + JAVA_OPTS: "-Xms${SYSTEM_JVM_XMS} -Xmx${SYSTEM_JVM_XMX} ${JVM_COMMON_OPTS}" + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + NACOS_SERVER_ADDR: ${NACOS_HOST}:${NACOS_PORT} + MYSQL_HOST: ${MYSQL_HOST} + MYSQL_PORT: ${MYSQL_PORT} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_PASSWORD: ${REDIS_PASSWORD} + TZ: ${TZ} + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: ${SYSTEM_MEMORY_LIMIT} + cpus: '1.0' + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:48081/actuator/health"] + interval: ${HEALTH_CHECK_INTERVAL} + timeout: ${HEALTH_CHECK_TIMEOUT} + retries: ${HEALTH_CHECK_RETRIES} + start_period: ${HEALTH_CHECK_START_PERIOD} + + viewsh-module-infra-server: + image: ${REGISTRY_HOST}/viewsh-module-infra-server:${IMAGE_TAG} + container_name: aiot-infra-server + restart: unless-stopped + network_mode: host + environment: + JAVA_OPTS: "-Xms${INFRA_JVM_XMS} -Xmx${INFRA_JVM_XMX} ${JVM_COMMON_OPTS}" + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + NACOS_SERVER_ADDR: ${NACOS_HOST}:${NACOS_PORT} + MYSQL_HOST: ${MYSQL_HOST} + MYSQL_PORT: ${MYSQL_PORT} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_PASSWORD: ${REDIS_PASSWORD} + TZ: ${TZ} + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: ${INFRA_MEMORY_LIMIT} + cpus: '1.0' + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:48082/actuator/health"] + interval: ${HEALTH_CHECK_INTERVAL} + timeout: ${HEALTH_CHECK_TIMEOUT} + retries: ${HEALTH_CHECK_RETRIES} + start_period: ${HEALTH_CHECK_START_PERIOD} + + viewsh-module-iot-server: + image: ${REGISTRY_HOST}/viewsh-module-iot-server:${IMAGE_TAG} + container_name: aiot-iot-server + restart: unless-stopped + network_mode: host + environment: + JAVA_OPTS: "-Xms${IOT_SERVER_JVM_XMS} -Xmx${IOT_SERVER_JVM_XMX} ${JVM_COMMON_OPTS}" + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + NACOS_SERVER_ADDR: ${NACOS_HOST}:${NACOS_PORT} + MYSQL_HOST: ${MYSQL_HOST} + MYSQL_PORT: ${MYSQL_PORT} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_PASSWORD: ${REDIS_PASSWORD} + ROCKETMQ_NAMESRV_ADDR: ${ROCKETMQ_NAMESRV_HOST}:${ROCKETMQ_NAMESRV_PORT} + TZ: ${TZ} + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: ${IOT_SERVER_MEMORY_LIMIT} + cpus: '1.5' + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:48083/actuator/health"] + interval: ${HEALTH_CHECK_INTERVAL} + timeout: ${HEALTH_CHECK_TIMEOUT} + retries: ${HEALTH_CHECK_RETRIES} + start_period: ${HEALTH_CHECK_START_PERIOD} + + viewsh-module-iot-gateway: + image: ${REGISTRY_HOST}/viewsh-module-iot-gateway:${IMAGE_TAG} + container_name: aiot-iot-gateway + restart: unless-stopped + network_mode: host + environment: + JAVA_OPTS: "-Xms${IOT_GATEWAY_JVM_XMS} -Xmx${IOT_GATEWAY_JVM_XMX} ${JVM_COMMON_OPTS}" + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_PASSWORD: ${REDIS_PASSWORD} + ROCKETMQ_NAMESRV_ADDR: ${ROCKETMQ_NAMESRV_HOST}:${ROCKETMQ_NAMESRV_PORT} + TZ: ${TZ} + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: ${IOT_GATEWAY_MEMORY_LIMIT} + cpus: '1.5' + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:48084/actuator/health"] + interval: ${HEALTH_CHECK_INTERVAL} + timeout: ${HEALTH_CHECK_TIMEOUT} + retries: ${HEALTH_CHECK_RETRIES} + start_period: ${HEALTH_CHECK_START_PERIOD} + + viewsh-module-ops-server: + image: ${REGISTRY_HOST}/viewsh-module-ops-server:${IMAGE_TAG} + container_name: aiot-ops-server + restart: unless-stopped + network_mode: host + environment: + JAVA_OPTS: "-Xms${OPS_JVM_XMS} -Xmx${OPS_JVM_XMX} ${JVM_COMMON_OPTS}" + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + NACOS_SERVER_ADDR: ${NACOS_HOST}:${NACOS_PORT} + MYSQL_HOST: ${MYSQL_HOST} + MYSQL_PORT: ${MYSQL_PORT} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_PASSWORD: ${REDIS_PASSWORD} + TZ: ${TZ} + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: ${OPS_MEMORY_LIMIT} + cpus: '0.5' + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:48085/actuator/health"] + interval: ${HEALTH_CHECK_INTERVAL} + timeout: ${HEALTH_CHECK_TIMEOUT} + retries: ${HEALTH_CHECK_RETRIES} + start_period: ${HEALTH_CHECK_START_PERIOD} diff --git a/docker/Dockerfile.template b/docker/Dockerfile.template new file mode 100644 index 0000000..685a9d6 --- /dev/null +++ b/docker/Dockerfile.template @@ -0,0 +1,83 @@ +# 多阶段构建 Dockerfile 模板 +# 适用于所有 Spring Boot 服务 + +# ============ 构建阶段 ============ +FROM eclipse-temurin:17-jdk-alpine AS builder + +# 安装必要工具 +RUN apk add --no-cache maven + +# 设置工作目录 +WORKDIR /build + +# 复制 Maven 配置文件(利用 Docker 缓存) +COPY pom.xml . +COPY viewsh-dependencies/pom.xml viewsh-dependencies/ +COPY viewsh-framework/pom.xml viewsh-framework/ +COPY viewsh-gateway/pom.xml viewsh-gateway/ +COPY viewsh-module-system/pom.xml viewsh-module-system/ +COPY viewsh-module-infra/pom.xml viewsh-module-infra/ +COPY viewsh-module-iot/pom.xml viewsh-module-iot/ +COPY viewsh-module-ops/pom.xml viewsh-module-ops/ +COPY viewsh-server/pom.xml viewsh-server/ + +# 下载依赖(利用缓存层) +RUN mvn dependency:go-offline -B || true + +# 复制源代码 +COPY . . + +# 构建参数:指定要构建的模块 +ARG MODULE_NAME +ARG SKIP_TESTS=true + +# 编译打包 +RUN if [ "$SKIP_TESTS" = "true" ]; then \ + mvn clean package -pl ${MODULE_NAME} -am -DskipTests -B; \ + else \ + mvn clean package -pl ${MODULE_NAME} -am -B; \ + fi + +# ============ 运行阶段 ============ +FROM eclipse-temurin:17-jre-alpine + +# 构建参数 +ARG MODULE_NAME +ARG JAR_NAME +ARG APP_PORT=48080 + +# 元数据标签 +LABEL maintainer="XW-AIOT Team" +LABEL service="${MODULE_NAME}" + +# 创建非 root 用户 +RUN addgroup -g 1000 appuser && \ + adduser -D -u 1000 -G appuser appuser + +# 创建应用目录 +RUN mkdir -p /app/logs /app/config && \ + chown -R appuser:appuser /app + +WORKDIR /app + +# 从构建阶段复制 JAR 文件 +COPY --from=builder --chown=appuser:appuser /build/${MODULE_NAME}/target/${JAR_NAME}.jar app.jar + +# 切换到非 root 用户 +USER appuser + +# 环境变量 +ENV TZ=Asia/Shanghai \ + JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/heap-dump.hprof" \ + SPRING_PROFILES_ACTIVE=prod \ + APP_ARGS="" + +# 暴露端口 +EXPOSE ${APP_PORT} + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:${APP_PORT}/actuator/health || exit 1 + +# 启动应用 +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar ${APP_ARGS}"] diff --git a/docker/services-config.json b/docker/services-config.json new file mode 100644 index 0000000..db4fb79 --- /dev/null +++ b/docker/services-config.json @@ -0,0 +1,165 @@ +{ + "services": { + "viewsh-gateway": { + "module": "viewsh-gateway", + "jarName": "viewsh-gateway", + "port": 48080, + "memory": { + "limit": "768m", + "reservation": "512m", + "jvm": { + "xms": "512m", + "xmx": "768m" + } + }, + "cpu": "1.0", + "priority": "core", + "healthCheck": "/actuator/health", + "dependencies": ["nacos", "redis"] + }, + "viewsh-module-system-server": { + "module": "viewsh-module-system/viewsh-module-system-server", + "jarName": "viewsh-module-system-server", + "port": 48081, + "memory": { + "limit": "768m", + "reservation": "512m", + "jvm": { + "xms": "512m", + "xmx": "768m" + } + }, + "cpu": "1.0", + "priority": "core", + "healthCheck": "/actuator/health", + "dependencies": ["mysql", "redis", "nacos"] + }, + "viewsh-module-infra-server": { + "module": "viewsh-module-infra/viewsh-module-infra-server", + "jarName": "viewsh-module-infra-server", + "port": 48082, + "memory": { + "limit": "768m", + "reservation": "512m", + "jvm": { + "xms": "512m", + "xmx": "768m" + } + }, + "cpu": "1.0", + "priority": "core", + "healthCheck": "/actuator/health", + "dependencies": ["mysql", "redis", "nacos"] + }, + "viewsh-module-iot-server": { + "module": "viewsh-module-iot/viewsh-module-iot-server", + "jarName": "viewsh-module-iot-server", + "port": 48083, + "memory": { + "limit": "1024m", + "reservation": "768m", + "jvm": { + "xms": "768m", + "xmx": "1024m" + } + }, + "cpu": "1.5", + "priority": "core", + "healthCheck": "/actuator/health", + "dependencies": ["mysql", "redis", "nacos", "rocketmq"], + "description": "IoT 业务服务,处理设备数据,需要较大内存" + }, + "viewsh-module-iot-gateway": { + "module": "viewsh-module-iot/viewsh-module-iot-gateway", + "jarName": "viewsh-module-iot-gateway", + "port": 48084, + "memory": { + "limit": "1024m", + "reservation": "768m", + "jvm": { + "xms": "768m", + "xmx": "1024m" + } + }, + "cpu": "1.5", + "priority": "core", + "healthCheck": "/actuator/health", + "dependencies": ["redis", "rocketmq"], + "description": "IoT 设备网关,处理设备连接,需要较大内存" + }, + "viewsh-module-ops-server": { + "module": "viewsh-module-ops/viewsh-module-ops-server", + "jarName": "viewsh-module-ops-server", + "port": 48085, + "memory": { + "limit": "384m", + "reservation": "256m", + "jvm": { + "xms": "256m", + "xmx": "384m" + } + }, + "cpu": "0.5", + "priority": "core", + "healthCheck": "/actuator/health", + "dependencies": ["mysql", "redis", "nacos"] + } + }, + "middleware": { + "mysql": { + "image": "mysql:8.0", + "port": 3306, + "memory": { + "limit": "2048m", + "reservation": "1024m" + }, + "cpu": "1.0", + "volumes": ["mysql-data:/var/lib/mysql"] + }, + "redis": { + "image": "redis:7-alpine", + "port": 6379, + "memory": { + "limit": "512m", + "reservation": "256m" + }, + "cpu": "0.5", + "volumes": ["redis-data:/data"] + }, + "nacos": { + "image": "nacos/nacos-server:v2.2.3", + "port": 8848, + "memory": { + "limit": "1024m", + "reservation": "768m" + }, + "cpu": "1.0", + "volumes": ["nacos-data:/home/nacos/data"] + }, + "rocketmq-namesrv": { + "image": "apache/rocketmq:5.1.4", + "port": 9876, + "memory": { + "limit": "512m", + "reservation": "256m" + }, + "cpu": "0.5" + }, + "rocketmq-broker": { + "image": "apache/rocketmq:5.1.4", + "port": 10911, + "memory": { + "limit": "1536m", + "reservation": "1024m" + }, + "cpu": "1.0", + "volumes": ["rocketmq-data:/home/rocketmq/store"] + } + }, + "resourceSummary": { + "totalApplicationMemory": "4.5GB", + "totalMiddlewareMemory": "5.5GB", + "totalEstimatedMemory": "10GB", + "note": "内存配置可在 .env 文件中调整,核心服务(iot-server, iot-gateway)已分配更大内存" + } +} diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100644 index 0000000..5107e11 --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# ============================================ +# AIOT Platform - 清理脚本 +# 清理旧镜像和容器,释放存储空间 +# ============================================ + +set -e + +# 颜色输出 +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_info "=========================================" +log_info "AIOT Platform 清理开始" +log_info "=========================================" + +# 显示当前磁盘使用情况 +log_info "当前磁盘使用情况:" +df -h | grep -E "Filesystem|/$" +echo "" +log_info "当前 Docker 磁盘使用:" +docker system df +echo "" + +# 清理停止的容器 +log_info "清理停止的容器..." +docker container prune -f + +# 清理悬空镜像 +log_info "清理悬空镜像..." +docker image prune -f + +# 清理旧版本镜像(保留最近 3 个版本) +log_info "清理旧版本镜像(保留最近 3 个版本)..." + +SERVICES="viewsh-gateway viewsh-module-system-server viewsh-module-infra-server viewsh-module-iot-server viewsh-module-iot-gateway viewsh-module-ops-server" + +for service in $SERVICES; do + log_info "处理服务: ${service}" + + # 获取所有镜像,按时间排序,删除除了最新 3 个之外的所有镜像 + docker images "localhost:5000/${service}" --format "{{.ID}} {{.Tag}}" | \ + grep -v "latest" | \ + tail -n +4 | \ + awk '{print $1}' | \ + xargs -r docker rmi -f 2>/dev/null || true +done + +# 清理未使用的卷(谨慎使用) +log_warn "是否清理未使用的卷? (y/N)" +read -r response +if [ "$response" = "y" ] || [ "$response" = "Y" ]; then + log_info "清理未使用的卷..." + docker volume prune -f +fi + +# 清理构建缓存 +log_info "清理 Docker 构建缓存..." +docker builder prune -f --filter "until=24h" + +# 显示清理后的磁盘使用情况 +echo "" +log_info "=========================================" +log_info "清理完成" +log_info "=========================================" +echo "" +log_info "清理后磁盘使用情况:" +df -h | grep -E "Filesystem|/$" +echo "" +log_info "清理后 Docker 磁盘使用:" +docker system df diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..1111fb9 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,230 @@ +#!/bin/bash + +# ============================================ +# AIOT Platform - 部署脚本 +# 滚动更新部署,支持健康检查和自动回滚 +# ============================================ + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 配置 +COMPOSE_FILE="docker-compose.core.yml" +REGISTRY="localhost:5000" +HEALTH_CHECK_TIMEOUT=120 +HEALTH_CHECK_INTERVAL=5 + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查服务健康状态 +check_health() { + local service=$1 + local container_name="aiot-${service}" + local elapsed=0 + + log_info "等待 ${service} 健康检查..." + + while [ $elapsed -lt $HEALTH_CHECK_TIMEOUT ]; do + if docker inspect --format='{{.State.Health.Status}}' "$container_name" 2>/dev/null | grep -q "healthy"; then + log_info "${service} 健康检查通过 ✓" + return 0 + fi + + sleep $HEALTH_CHECK_INTERVAL + elapsed=$((elapsed + HEALTH_CHECK_INTERVAL)) + echo -n "." + done + + echo "" + log_error "${service} 健康检查超时 ✗" + return 1 +} + +# 备份当前运行的镜像标签 +backup_current_tags() { + log_info "备份当前镜像标签..." + + docker-compose -f "$COMPOSE_FILE" config --services | while read service; do + local current_image=$(docker inspect "aiot-${service}" --format='{{.Config.Image}}' 2>/dev/null || echo "") + if [ -n "$current_image" ]; then + echo "${service}=${current_image}" >> .deploy_backup + fi + done + + log_info "备份完成: .deploy_backup" +} + +# 部署单个服务 +deploy_service() { + local service=$1 + + log_info "=========================================" + log_info "部署服务: ${service}" + log_info "=========================================" + + # 拉取最新镜像 + log_info "拉取最新镜像..." + if ! docker-compose -f "$COMPOSE_FILE" pull "$service"; then + log_error "拉取镜像失败" + return 1 + fi + + # 启动新容器 + log_info "启动新容器..." + if ! docker-compose -f "$COMPOSE_FILE" up -d "$service"; then + log_error "启动容器失败" + return 1 + fi + + # 健康检查 + if ! check_health "$service"; then + log_error "${service} 部署失败,准备回滚..." + return 1 + fi + + log_info "${service} 部署成功 ✓" + return 0 +} + +# 回滚服务 +rollback_service() { + local service=$1 + + log_warn "回滚服务: ${service}" + + # 从备份文件读取之前的镜像 + if [ -f .deploy_backup ]; then + local backup_image=$(grep "^${service}=" .deploy_backup | cut -d'=' -f2) + + if [ -n "$backup_image" ]; then + log_info "回滚到镜像: ${backup_image}" + + docker-compose -f "$COMPOSE_FILE" stop "$service" + docker tag "$backup_image" "${REGISTRY}/${service}:latest" + docker-compose -f "$COMPOSE_FILE" up -d "$service" + + if check_health "$service"; then + log_info "${service} 回滚成功 ✓" + return 0 + fi + fi + fi + + log_error "${service} 回滚失败 ✗" + return 1 +} + +# 主部署流程 +main() { + log_info "=========================================" + log_info "AIOT Platform 部署开始" + log_info "=========================================" + + # 检查 .env 文件 + if [ ! -f .env ]; then + log_error ".env 文件不存在,请先复制 .env.example 并配置" + exit 1 + fi + + # 加载环境变量 + source .env + + # 备份当前状态 + rm -f .deploy_backup + backup_current_tags + + # 获取要部署的服务列表 + local services_to_deploy="" + + if [ $# -eq 0 ]; then + # 部署所有核心服务 + services_to_deploy=$(docker-compose -f "$COMPOSE_FILE" config --services | grep -v -E "mysql|redis|nacos|rocketmq") + else + # 部署指定服务 + services_to_deploy="$@" + fi + + log_info "待部署服务: ${services_to_deploy}" + + # 部署服务 + local failed_services="" + + for service in $services_to_deploy; do + if ! deploy_service "$service"; then + failed_services="${failed_services} ${service}" + + # 尝试回滚 + rollback_service "$service" + fi + done + + # 清理旧镜像 + log_info "清理悬空镜像..." + docker image prune -f + + # 部署结果 + echo "" + log_info "=========================================" + if [ -z "$failed_services" ]; then + log_info "所有服务部署成功 ✓" + log_info "=========================================" + rm -f .deploy_backup + exit 0 + else + log_error "以下服务部署失败: ${failed_services}" + log_error "=========================================" + exit 1 + fi +} + +# 显示帮助 +show_help() { + cat << EOF +用法: $0 [服务名...] + +部署 AIOT Platform 服务到生产环境 + +参数: + 无参数 部署所有应用服务 + 服务名... 部署指定的服务 + +示例: + $0 # 部署所有服务 + $0 viewsh-gateway # 仅部署 gateway + $0 viewsh-module-iot-server viewsh-module-iot-gateway # 部署多个服务 + +服务列表: + - viewsh-gateway + - viewsh-module-system-server + - viewsh-module-infra-server + - viewsh-module-iot-server + - viewsh-module-iot-gateway + - viewsh-module-ops-server + +EOF +} + +# 参数处理 +if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + show_help + exit 0 +fi + +# 执行主流程 +main "$@" diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100644 index 0000000..cb0c74a --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# ============================================ +# AIOT Platform - 回滚脚本 +# 快速回滚到上一个版本 +# ============================================ + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查备份文件 +if [ ! -f .deploy_backup ]; then + log_error "未找到备份文件 .deploy_backup" + log_error "无法执行回滚操作" + exit 1 +fi + +log_info "=========================================" +log_info "AIOT Platform 回滚开始" +log_info "=========================================" + +# 读取备份并回滚 +while IFS='=' read -r service image; do + log_info "回滚服务: ${service} -> ${image}" + + docker-compose -f docker-compose.core.yml stop "$service" + docker tag "$image" "localhost:5000/${service}:latest" + docker-compose -f docker-compose.core.yml up -d "$service" + + log_info "${service} 回滚完成 ✓" +done < .deploy_backup + +log_info "=========================================" +log_info "回滚完成" +log_info "========================================="