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..9e69bf6 --- /dev/null +++ b/.env.example @@ -0,0 +1,190 @@ +# ============================================ +# 环境变量配置文件 +# 复制此文件为 .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_DATABASE=0 + +# 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=5 +HEALTH_CHECK_START_PERIOD=90s + +# ============ XXL-JOB 配置(暂时注释)============ +# 如需部署 XXL-JOB Admin,取消以下注释 +# XXL_JOB_ADMIN_ADDRESSES=http://127.0.0.1:9090/xxl-job-admin +# XXL_JOB_ACCESS_TOKEN=default_token + +# ============ 微信配置(如果需要微信支付功能) ============ +# 如果不使用微信支付,保持这些为空或注释掉 +# WX_MP_APP_ID= +# WX_MP_SECRET= +# WX_MP_TOKEN= +# WX_MP_AES_KEY= + +# ============ IoT Gateway 特有配置 ============ +# 注意: iot-gateway 已支持 Nacos,同时保留环境变量配置作为补充 + +# 消息总线类型 +IOT_MESSAGE_BUS_TYPE=redis + +# 设备 RPC 配置(调用 iot-server 微服务) +IOT_RPC_URL=http://127.0.0.1:48091 +IOT_RPC_CONNECT_TIMEOUT=30s +IOT_RPC_READ_TIMEOUT=30s + +# 设备 Token 配置(生产环境必须修改为强密钥,至少32位) +IOT_TOKEN_SECRET=viewshIotGatewayTokenSecret123456789 +IOT_TOKEN_EXPIRATION=7d + +# HTTP 协议配置 +IOT_HTTP_ENABLED=true +IOT_HTTP_PORT=8092 + +# MQTT 协议配置 +IOT_MQTT_ENABLED=true +IOT_MQTT_PORT=1883 +IOT_MQTT_MAX_MESSAGE_SIZE=8192 +IOT_MQTT_CONNECT_TIMEOUT=60 +IOT_MQTT_SSL_ENABLED=false + +# TCP 协议配置 +IOT_TCP_ENABLED=false +IOT_TCP_PORT=8091 +IOT_TCP_KEEPALIVE_TIMEOUT=30000 +IOT_TCP_MAX_CONNECTIONS=1000 +IOT_TCP_SSL_ENABLED=false + +# EMQX 协议配置(外部 MQTT Broker) +IOT_EMQX_ENABLED=false +IOT_EMQX_HTTP_PORT=8090 +IOT_EMQX_MQTT_HOST=127.0.0.1 +IOT_EMQX_MQTT_PORT=1883 +IOT_EMQX_MQTT_USERNAME=admin +IOT_EMQX_MQTT_PASSWORD=public +IOT_EMQX_MQTT_CLIENT_ID=iot-gateway-mqtt +IOT_EMQX_MQTT_SSL=false +IOT_EMQX_TRUST_ALL=false +IOT_EMQX_WILL_ENABLED=true + +# IoT Gateway 日志配置 +LOG_LEVEL_IOT_GATEWAY=INFO +LOG_LEVEL_EMQX=INFO +LOG_LEVEL_HTTP=INFO +LOG_LEVEL_MQTT=INFO + +# ============ 资源限制说明 ============ +# 当前配置预计总内存占用: +# - 应用服务: ~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/.gitignore b/.gitignore index e55eb64..09c037a 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ functions/mock screenshot .firebase sessionStore +.claude/ diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..80af5e1 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,1035 @@ +// ============================================ +// AIOT Platform - Jenkins Pipeline (Optimized Edition) +// ============================================ + +pipeline { + agent any + + options { + buildDiscarder(logRotator( + numToKeepStr: '10', + artifactNumToKeepStr: '5' + )) + disableConcurrentBuilds() + timeout(time: 90, unit: 'MINUTES') + timestamps() + retry(1) + } + + environment { + // 镜像仓库配置 + REGISTRY = 'localhost:5000' + DEPS_IMAGE = "${REGISTRY}/aiot-deps:latest" + + // 服务配置 + CORE_SERVICES = 'viewsh-gateway,viewsh-module-system-server,viewsh-module-infra-server,viewsh-module-iot-server,viewsh-module-iot-gateway' + + // 部署配置 + DEPLOY_HOST = '172.19.0.1' + DEPLOY_PATH = '/opt/aiot-platform-cloud' + SSH_KEY = '/var/jenkins_home/.ssh/id_rsa' + + // 性能配置 - 将动态调整 + BUILD_TIMEOUT = 45 + DEPLOY_TIMEOUT = 10 + HEALTH_CHECK_TIMEOUT = 180 + HEALTH_CHECK_INTERVAL = 10 + + // 【优化1】Maven 缓存配置 + MAVEN_CACHE_VOLUME = 'jenkins-maven-cache' + MAVEN_OPTS = '-Dmaven.repo.local=/var/jenkins_home/.m2/repository' + + // 【优化7】回滚配置 + ROLLBACK_ENABLED = 'true' + KEEP_PREVIOUS_IMAGES = 'true' + } + + stages { + stage('Initialize') { + steps { + script { + // 【优化6】记录开始时间 + env.PIPELINE_START_TIME = System.currentTimeMillis() + + echo "==========================================" + echo " AIOT Platform - CI/CD Pipeline (Optimized)" + echo "==========================================" + echo "Branch: ${env.BRANCH_NAME}" + echo "Build: #${env.BUILD_NUMBER}" + echo "Workspace: ${env.WORKSPACE}" + echo "Start Time: ${new Date()}" + echo "==========================================" + + // 【优化2】动态检测系统资源 + detectSystemResources() + } + } + } + + stage('Checkout') { + steps { + script { + def stageStartTime = System.currentTimeMillis() + + retry(3) { + checkout scm + } + + def shortCommit = sh( + script: 'git rev-parse --short HEAD', + returnStdout: true + ).trim() + + env.IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}-${shortCommit}" + env.PREVIOUS_IMAGE_TAG = getPreviousImageTag() + env.GIT_COMMIT_MSG = sh( + script: 'git log -1 --pretty=%B', + returnStdout: true + ).trim() + + echo "📦 Commit: ${shortCommit}" + echo "📝 Message: ${env.GIT_COMMIT_MSG}" + echo "🏷️ Current Tag: ${env.IMAGE_TAG}" + echo "🔖 Previous Tag: ${env.PREVIOUS_IMAGE_TAG}" + + // 【优化6】记录阶段耗时 + recordStageMetrics('Checkout', stageStartTime) + } + } + } + + stage('Detect Changes') { + steps { + script { + def stageStartTime = System.currentTimeMillis() + + def changedFiles = getChangedFiles() + echo "📝 Changed files: ${changedFiles.size()} files" + + env.SERVICES_TO_BUILD = detectServicesToBuild(changedFiles) + env.DEPS_CHANGED = checkIfDepsChanged(changedFiles) + + // Check if deps image exists + if (env.DEPS_CHANGED == 'false') { + def depsExists = sh( + script: "docker manifest inspect ${env.DEPS_IMAGE} > /dev/null 2>&1", + returnStatus: true + ) + if (depsExists != 0) { + env.DEPS_CHANGED = 'true' + echo "📦 Deps image not found, will rebuild" + } + } + + if (env.SERVICES_TO_BUILD.isEmpty()) { + echo "⏭️ No changes detected, skipping build" + currentBuild.result = 'SUCCESS' + return + } + + echo "🔄 Services to build: ${env.SERVICES_TO_BUILD}" + echo "📦 Deps changed: ${env.DEPS_CHANGED}" + + env.SERVICES_TO_BUILD.split(',').each { service -> + def module = getModulePathForService(service) + echo " - ${service} (${module})" + } + + recordStageMetrics('Detect Changes', stageStartTime) + } + } + } + + stage('Pre-build Check') { + when { + expression { env.SERVICES_TO_BUILD != '' } + } + steps { + script { + def stageStartTime = System.currentTimeMillis() + + echo "🔍 Running pre-build checks..." + + // 检查 Docker + sh "docker version >/dev/null 2>&1 || { echo '❌ Docker not available'; exit 1; }" + + // 【优化1】检查并创建 Maven 缓存卷 + sh """ + if ! docker volume inspect ${env.MAVEN_CACHE_VOLUME} >/dev/null 2>&1; then + echo "📦 Creating Maven cache volume: ${env.MAVEN_CACHE_VOLUME}" + docker volume create ${env.MAVEN_CACHE_VOLUME} + else + echo "✅ Maven cache volume exists: ${env.MAVEN_CACHE_VOLUME}" + fi + """ + + // 检查磁盘空间 + def diskUsage = sh( + script: "df ${env.WORKSPACE} | tail -1 | awk '{print \$5}' | sed 's/%//'", + returnStdout: true + ).trim() as int + + if (diskUsage > 80) { + echo "⚠️ Disk usage is ${diskUsage}%, cleaning up..." + sh "docker system prune -f --volumes || true" + } + + // 检查镜像仓库连接 + sh """ + curl -f ${env.REGISTRY}/v2/ >/dev/null 2>&1 || \ + { echo '⚠️ Registry not accessible, will continue...'; } + """ + + echo "✅ Pre-build checks passed" + recordStageMetrics('Pre-build Check', stageStartTime) + } + } + } + + stage('Build Dependencies Image') { + when { + expression { + env.SERVICES_TO_BUILD != '' && + env.DEPS_CHANGED == 'true' + } + } + steps { + script { + def stageStartTime = System.currentTimeMillis() + + echo "📦 Building dependencies base image with Maven cache..." + timeout(time: 15, unit: 'MINUTES') { + // 【优化1】使用 Maven 缓存卷加速依赖下载 + sh """ + set -e + echo "Building ${env.DEPS_IMAGE} with cache..." + docker build \ + -f docker/Dockerfile.deps \ + -t ${env.DEPS_IMAGE} \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + --build-arg MAVEN_OPTS="${env.MAVEN_OPTS}" \ + . + + docker push ${env.DEPS_IMAGE} + echo "✅ Dependencies image built and pushed" + """ + } + + recordStageMetrics('Build Dependencies Image', stageStartTime) + } + } + } + + stage('Build Services') { + when { + expression { env.SERVICES_TO_BUILD != '' } + } + steps { + script { + def stageStartTime = System.currentTimeMillis() + + def servicesToBuild = env.SERVICES_TO_BUILD.split(',') + echo "🔨 Building ${servicesToBuild.size()} services (parallelism: ${env.MAX_PARALLEL_BUILDS})" + + // 【优化2】动态并行构建 + def buildTasks = [:] + servicesToBuild.each { service -> + buildTasks[service] = { + buildServiceWithRetry(service) + } + } + + parallel buildTasks + + echo "✅ All services built successfully" + + // 显示构建后的镜像信息 + sh """ + echo "📊 Built images:" + ${env.SERVICES_TO_BUILD.split(',').collect { "docker images ${env.REGISTRY}/${it} --format ' {{.Repository}}:{{.Tag}} - {{.Size}}'" }.join('\n ')} + """ + + recordStageMetrics('Build Services', stageStartTime) + } + } + } + + stage('Deploy') { + when { + allOf { + expression { env.SERVICES_TO_BUILD != '' } + branch 'master' + } + } + steps { + script { + def stageStartTime = System.currentTimeMillis() + + def servicesToDeploy = env.SERVICES_TO_BUILD.split(',') + def sortedServices = sortServicesByDependency(servicesToDeploy) + + echo "🚀 Deploying ${sortedServices.size()} services in dependency order" + sortedServices.eachWithIndex { service, index -> + echo " ${index + 1}. ${service}" + } + + // 【优化7】部署前备份当前镜像标签 + if (env.ROLLBACK_ENABLED == 'true') { + backupCurrentDeployment(sortedServices) + } + + // 【新增】同步最新的 docker-compose.core.yml 到部署服务器 + echo "📂 Syncing docker-compose.core.yml to deploy host..." + sh "scp -o StrictHostKeyChecking=no -i ${env.SSH_KEY} docker-compose.core.yml root@${env.DEPLOY_HOST}:${env.DEPLOY_PATH}/" + + try { + // 串行部署(保证依赖关系) + sortedServices.each { service -> + deployServiceWithTimeout(service) + } + + echo "🚀 All services deployed successfully!" + + } catch (Exception e) { + // 【优化7】部署失败时自动回滚 + if (env.ROLLBACK_ENABLED == 'true') { + echo "❌ Deployment failed: ${e.message}" + echo "🔄 Initiating automatic rollback..." + rollbackDeployment(sortedServices) + } + throw e + } + + recordStageMetrics('Deploy', stageStartTime) + } + } + } + + stage('Final Health Check') { + when { + allOf { + expression { env.SERVICES_TO_BUILD != '' } + branch 'master' + } + } + steps { + script { + def stageStartTime = System.currentTimeMillis() + + echo "🏥 Running final health check for all services..." + + def servicesToCheck = env.SERVICES_TO_BUILD.split(',') + def healthCheckTasks = [:] + + servicesToCheck.each { service -> + healthCheckTasks[service] = { + checkServiceHealthWithRetry(service) + } + } + + try { + parallel healthCheckTasks + echo "✅ All services are healthy!" + } catch (Exception e) { + // 【优化7】健康检查失败时回滚 + if (env.ROLLBACK_ENABLED == 'true') { + echo "❌ Health check failed: ${e.message}" + echo "🔄 Initiating automatic rollback..." + rollbackDeployment(servicesToCheck) + } + throw e + } + + // 显示最终状态 + sh """ + echo "📊 Service Status:" + ${servicesToCheck.collect { + def container = getContainerNameForService(it) + "docker inspect --format='${it}: {{.State.Status}} ({{.State.Health.Status}})' ${container} 2>/dev/null || echo '${it}: not found'" + }.join('\n ')} + """ + + recordStageMetrics('Final Health Check', stageStartTime) + } + } + } + } + + post { + success { + script { + // 【优化6】计算总耗时并生成性能报告 + def totalDuration = System.currentTimeMillis() - env.PIPELINE_START_TIME.toLong() + generatePerformanceReport(totalDuration) + + echo """ + ========================================== + ✅ BUILD SUCCESS + ========================================== + 📦 Services: ${env.SERVICES_TO_BUILD} + 🏷️ Tag: ${env.IMAGE_TAG} + ⏱️ Duration: ${formatDuration(totalDuration)} + ========================================== + """ + } + } + failure { + script { + def totalDuration = System.currentTimeMillis() - env.PIPELINE_START_TIME.toLong() + + echo """ + ========================================== + ❌ BUILD FAILED + ========================================== + 📦 Services: ${env.SERVICES_TO_BUILD ?: 'None'} + 🏷️ Tag: ${env.IMAGE_TAG ?: 'Unknown'} + 🔖 Rollback Tag: ${env.PREVIOUS_IMAGE_TAG ?: 'N/A'} + ⏱️ Duration: ${formatDuration(totalDuration)} + ⚠️ Please check the logs above + ========================================== + """ + + // 失败时收集诊断信息 + sh ''' + echo "=== Docker System Info ===" + docker system df + echo "" + echo "=== Disk Usage ===" + df -h | grep -E "/$|/var" + echo "" + echo "=== Recent Containers ===" + docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.CreatedAt}}" | head -10 + ''' + } + } + always { + script { + echo "🧹 Cleaning up..." + + // 清理悬空的镜像(但保留带标签的镜像用于回滚) + if (env.KEEP_PREVIOUS_IMAGES == 'true') { + sh "docker image prune -f --filter 'dangling=true' || true" + } else { + sh "docker image prune -f || true" + } + + // 清理超过30天的构建日志 + sh """ + find ${env.WORKSPACE} -name '*.log' -mtime +30 -delete 2>/dev/null || true + """ + + echo "📊 Final System Status:" + sh 'df -h | grep -E "/$|/var" || true' + sh 'docker system df || true' + + // 【优化6】保存性能指标到文件 + archivePerformanceMetrics() + } + } + } +} + +// ============================================ +// 辅助函数 +// ============================================ + +// 获取变更的文件列表 +def getChangedFiles() { + def changedFiles = sh( + script: ''' + PREV=$(git rev-parse HEAD~1 2>/dev/null || echo "") + [ -z "$PREV" ] && echo "all" || git diff --name-only $PREV HEAD + ''', + returnStdout: true + ).trim() + + if (changedFiles == 'all') { + return ['all'] as List + } + + return changedFiles.split('\n') as List +} + +// 检测是否需要重建依赖镜像 +@NonCPS +def checkIfDepsChanged(List changedFiles) { + if (changedFiles.contains('all')) { + return 'true' + } + + def depsFiles = ['pom.xml', 'viewsh-dependencies', 'viewsh-framework', 'docker/Dockerfile.deps'] + return depsFiles.any { changedFiles.contains(it) } ? 'true' : 'false' +} + +// 检测需要构建的服务 +def detectServicesToBuild(List changedFiles) { + if (changedFiles.contains('all')) { + return env.CORE_SERVICES + } + + def triggerAllFiles = ['pom.xml', 'viewsh-framework', 'viewsh-dependencies', 'Jenkinsfile', 'docker/'] + if (triggerAllFiles.any { triggerFile -> + changedFiles.any { changedFile -> + changedFile.startsWith(triggerFile) || changedFile == triggerFile + } + }) { + return env.CORE_SERVICES + } + + def changedServices = [] + def allServices = env.CORE_SERVICES.split(',') + + allServices.each { service -> + def modulePath = getModulePathForService(service) + def moduleDir = modulePath.split('/')[0] + + if (changedFiles.any { it.startsWith(moduleDir) }) { + changedServices.add(service) + } + } + + return changedServices.isEmpty() ? env.CORE_SERVICES : changedServices.join(',') +} + +// 检查依赖镜像是否存在 +def depsImageExists() { + def result = sh( + script: "docker manifest inspect ${env.DEPS_IMAGE} > /dev/null 2>&1", + returnStatus: true + ) + return result == 0 +} + +// 【优化2】动态检测系统资源并设置并行度 +def detectSystemResources() { + try { + // 获取 CPU 核心数 + def cpuCores = sh( + script: 'nproc 2>/dev/null || echo "2"', + returnStdout: true + ).trim() as int + + // 获取可用内存(GB) + def availableMemory = sh( + script: 'free -g | grep Mem | awk \'{print $7}\'', + returnStdout: true + ).trim() as int + + // 动态计算并行度 + // 规则:每个构建任务需要至少 2GB 内存和 1 个 CPU 核心 + def maxParallelByMemory = Math.max(1, (availableMemory / 2) as int) + def maxParallelByCpu = Math.max(1, cpuCores - 1) // 保留一个核心给系统 + + env.MAX_PARALLEL_BUILDS = Math.min(maxParallelByMemory, maxParallelByCpu).toString() + + echo """ + ======================================== + 📊 System Resources Detected: + ======================================== + CPU Cores: ${cpuCores} + Available Memory: ${availableMemory} GB + Max Parallel Builds: ${env.MAX_PARALLEL_BUILDS} + ======================================== + """ + } catch (Exception e) { + echo "⚠️ Failed to detect system resources: ${e.message}" + echo "Using default parallelism: 2" + env.MAX_PARALLEL_BUILDS = '2' + } +} + +// 【优化6】记录阶段性能指标 +def recordStageMetrics(String stageName, long startTime) { + def duration = System.currentTimeMillis() - startTime + def durationStr = formatDuration(duration) + + if (!env.STAGE_METRICS) { + env.STAGE_METRICS = "" + } + + env.STAGE_METRICS += "${stageName}:${duration}|" + echo "⏱️ Stage '${stageName}' completed in ${durationStr}" +} + +// 【优化6】格式化时长 +@NonCPS +def formatDuration(long milliseconds) { + def seconds = (milliseconds / 1000) as int + def minutes = (seconds / 60) as int + def hours = (minutes / 60) as int + + if (hours > 0) { + return "${hours}h ${minutes % 60}m ${seconds % 60}s" + } else if (minutes > 0) { + return "${minutes}m ${seconds % 60}s" + } else { + return "${seconds}s" + } +} + +// 【优化6】生成性能报告 +def generatePerformanceReport(long totalDuration) { + echo """ + ========================================== + 📊 PERFORMANCE REPORT + ========================================== + Total Duration: ${formatDuration(totalDuration)} + """ + + if (env.STAGE_METRICS) { + echo "Stage Breakdown:" + env.STAGE_METRICS.split('\\|').each { metric -> + if (metric) { + def parts = metric.split(':') + if (parts.size() == 2) { + def stageName = parts[0] + def duration = parts[1] as long + def percentage = (duration * 100 / totalDuration) as int + echo " - ${stageName.padRight(25)}: ${formatDuration(duration).padRight(10)} (${percentage}%)" + } + } + } + } + + echo "==========================================" +} + +// 【优化6】归档性能指标 +def archivePerformanceMetrics() { + try { + def metricsFile = "${env.WORKSPACE}/build-metrics-${env.BUILD_NUMBER}.json" + def metricsData = [ + buildNumber: env.BUILD_NUMBER, + timestamp: new Date().format('yyyy-MM-dd HH:mm:ss'), + branch: env.BRANCH_NAME, + imageTag: env.IMAGE_TAG, + servicesToBuild: env.SERVICES_TO_BUILD, + totalDuration: System.currentTimeMillis() - env.PIPELINE_START_TIME.toLong(), + stages: [:] + ] + + if (env.STAGE_METRICS) { + env.STAGE_METRICS.split('\\|').each { metric -> + if (metric) { + def parts = metric.split(':') + if (parts.size() == 2) { + metricsData.stages[parts[0]] = parts[1] as long + } + } + } + } + + // writeJSON file: metricsFile, json: metricsData + writeFile file: metricsFile, text: groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(metricsData)) + archiveArtifacts artifacts: "build-metrics-${env.BUILD_NUMBER}.json", allowEmptyArchive: true + + echo "✅ Performance metrics archived: ${metricsFile}" + } catch (Exception e) { + echo "⚠️ Failed to archive performance metrics: ${e.message}" + } +} + +// 【优化7】获取上一次成功部署的镜像标签 +def getPreviousImageTag() { + try { + // 从部署主机获取当前运行的镜像标签 + def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${env.SSH_KEY}" + def previousTag = sh( + script: """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + cd ${env.DEPLOY_PATH} + docker compose -f docker-compose.core.yml images --format json | \ + jq -r ".[0].Tag" | head -1 + ' 2>/dev/null || echo "latest" + """, + returnStdout: true + ).trim() + + return previousTag ?: 'latest' + } catch (Exception e) { + echo "⚠️ Failed to get previous image tag: ${e.message}" + return 'latest' + } +} + +// 【优化7】备份当前部署 +def backupCurrentDeployment(def services) { + echo "💾 Backing up current deployment state..." + + def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${env.SSH_KEY}" + + try { + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + cd ${env.DEPLOY_PATH} + + # 保存当前 docker-compose 配置 + cp docker-compose.core.yml docker-compose.core.yml.backup-${env.BUILD_NUMBER} + + # 记录当前运行的镜像 + docker compose -f docker-compose.core.yml images > deployment-state-${env.BUILD_NUMBER}.txt + + echo "✅ Backup completed: deployment-state-${env.BUILD_NUMBER}.txt" + ' + """ + + echo "✅ Current deployment backed up successfully" + } catch (Exception e) { + echo "⚠️ Failed to backup current deployment: ${e.message}" + } +} + +// 【优化7】回滚部署 +def rollbackDeployment(def services) { + echo """ + ========================================== + 🔄 INITIATING ROLLBACK + ========================================== + Rolling back to: ${env.PREVIOUS_IMAGE_TAG} + Services: ${services.join(', ')} + ========================================== + """ + + def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${env.SSH_KEY}" + + try { + services.each { service -> + echo "🔄 Rolling back ${service}..." + + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + cd ${env.DEPLOY_PATH} + + # 设置回滚镜像标签 + export IMAGE_TAG=${env.PREVIOUS_IMAGE_TAG} + + # 拉取旧版本镜像 + docker compose -f docker-compose.core.yml pull ${service} + + # 重启服务 + docker compose -f docker-compose.core.yml up -d ${service} + + echo "✅ ${service} rolled back to ${env.PREVIOUS_IMAGE_TAG}" + ' + """ + + // 等待服务启动 + sleep 5 + } + + echo """ + ========================================== + ✅ ROLLBACK COMPLETED + ========================================== + All services have been rolled back to: ${env.PREVIOUS_IMAGE_TAG} + ========================================== + """ + + } catch (Exception e) { + echo """ + ========================================== + ❌ ROLLBACK FAILED + ========================================== + Error: ${e.message} + Manual intervention required! + ========================================== + """ + throw e + } +} + +// 构建服务(带重试) +def buildServiceWithRetry(String service) { + retry(2) { + timeout(time: env.BUILD_TIMEOUT.toInteger(), unit: 'MINUTES') { + buildService(service) + } + } +} + +// 构建单个服务 +def buildService(String service) { + def modulePath = getModulePathForService(service) + def buildStartTime = System.currentTimeMillis() + + echo "" + echo "==========================================" + echo "🔨 Building ${service}" + echo "==========================================" + echo "Module: ${modulePath}" + echo "Registry: ${env.REGISTRY}" + echo "Tag: ${env.IMAGE_TAG}" + echo "==========================================" + + try { + // 【优化1】使用 Maven 缓存卷 + sh """ + set -e + set -x + + # 构建镜像(使用 Maven 缓存) + docker build \\ + -f docker/Dockerfile.service \\ + --build-arg DEPS_IMAGE=${env.DEPS_IMAGE} \\ + --build-arg MODULE_NAME=${modulePath} \\ + --build-arg JAR_NAME=${service} \\ + --build-arg SKIP_TESTS=true \\ + --build-arg MAVEN_OPTS="${env.MAVEN_OPTS}" \\ + -t ${env.REGISTRY}/${service}:${env.IMAGE_TAG} \\ + -t ${env.REGISTRY}/${service}:latest \\ + . + + # 推送镜像 + docker push ${env.REGISTRY}/${service}:${env.IMAGE_TAG} + docker push ${env.REGISTRY}/${service}:latest + + set +x + """ + + def buildDuration = System.currentTimeMillis() - buildStartTime + echo "✅ ${service} built and pushed successfully in ${formatDuration(buildDuration)}" + + // 获取镜像大小 + def imageSize = sh( + script: "docker images ${env.REGISTRY}/${service}:latest --format '{{.Size}}'", + returnStdout: true + ).trim() + + echo "📊 Image size: ${imageSize}" + + } catch (Exception e) { + echo "❌ Failed to build ${service}: ${e.message}" + + // 打印构建日志以便调试 + sh """ + echo "=== Docker Build Logs for ${service} ===" + docker logs ${service}-builder 2>/dev/null || true + """ + + throw e + } +} + +// 部署服务(带超时) +def deployServiceWithTimeout(String service) { + timeout(time: env.DEPLOY_TIMEOUT.toInteger(), unit: 'MINUTES') { + deployService(service) + } +} + +// 部署单个服务 +def deployService(String service) { + def containerName = getContainerNameForService(service) + def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${env.SSH_KEY}" + + echo "" + echo "==========================================" + echo "🚀 Deploying ${service}" + echo "==========================================" + echo "Container: ${containerName}" + echo "Host: ${env.DEPLOY_HOST}" + echo "==========================================" + + try { + // 部署服务 + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + set -e + cd ${env.DEPLOY_PATH} + + echo "📥 Pulling ${service}..." + docker compose -f docker-compose.core.yml pull ${service} + + echo "🔄 Restarting ${service}..." + docker compose -f docker-compose.core.yml up -d ${service} + + echo "⏳ Waiting for container to start..." + sleep 5 + ' + """ + + // 等待服务健康 + waitForServiceHealthy(containerName, service, sshOpts) + + echo "✅ ${service} deployed successfully" + + } catch (Exception e) { + echo "❌ Failed to deploy ${service}: ${e.message}" + + // 收集诊断信息 + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + echo "=== Container Status ===" + docker ps -a | grep ${containerName} || true + + echo "" + echo "=== Container Logs (last 50 lines) ===" + docker logs --tail 50 ${containerName} 2>/dev/null || true + + echo "" + echo "=== Service Health Status ===" + docker inspect --format="{{.State.Health.Status}}" ${containerName} 2>/dev/null || echo "not_found" + ' + """ + + throw e + } +} + +// 等待服务健康 +def waitForServiceHealthy(String containerName, String serviceName, String sshOpts) { + // iot-gateway 是轻量化网关,不需要健康检查,只检查容器是否运行 + if (serviceName == 'viewsh-module-iot-gateway') { + echo "⏳ Waiting for ${serviceName} to start (lightweight gateway, no health check)..." + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + set -e + sleep 5 + if docker ps | grep -q ${containerName}; then + echo "✅ ${serviceName} is running" + else + echo "❌ ${serviceName} is not running" + docker logs --tail 50 ${containerName} 2>/dev/null || true + exit 1 + fi + ' + """ + return + } + + def maxAttempts = env.HEALTH_CHECK_TIMEOUT.toInteger() / env.HEALTH_CHECK_INTERVAL.toInteger() + + echo "⏳ Waiting for ${serviceName} to be healthy (max ${env.HEALTH_CHECK_TIMEOUT}s)..." + + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + set -e + + for i in \$(seq 1 ${maxAttempts}); do + STATUS=\$(docker inspect --format="{{.State.Health.Status}}" ${containerName} 2>/dev/null || echo "starting") + + case "\$STATUS" in + healthy) + echo "✅ ${serviceName} is healthy" + exit 0 + ;; + unhealthy) + echo "❌ ${serviceName} is unhealthy" + echo "=== Last 100 lines of logs ===" + docker logs --tail 100 ${containerName} + exit 1 + ;; + starting) + ELAPSED=\$((i * ${env.HEALTH_CHECK_INTERVAL})) + echo "⏳ ${serviceName} is starting... (\${ELAPSED}s/${env.HEALTH_CHECK_TIMEOUT}s)" + ;; + *) + echo "⚠️ ${serviceName} status: \$STATUS" + ;; + esac + + sleep ${env.HEALTH_CHECK_INTERVAL} + done + + echo "❌ ${serviceName} health check timeout after ${env.HEALTH_CHECK_TIMEOUT}s" + echo "=== Full logs ===" + docker logs ${containerName} + exit 1 + ' + """ +} + +// 检查服务健康(带重试) +def checkServiceHealthWithRetry(String service) { + def containerName = getContainerNameForService(service) + def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${env.SSH_KEY}" + + retry(3) { + timeout(time: 2, unit: 'MINUTES') { + checkServiceHealth(containerName, service, sshOpts) + } + } +} + +// 检查服务健康状态 +def checkServiceHealth(String containerName, String serviceName, String sshOpts) { + // iot-gateway 是轻量化网关,不需要健康检查,只检查容器是否运行 + if (serviceName == 'viewsh-module-iot-gateway') { + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + if docker ps | grep -q ${containerName}; then + echo "✅ ${serviceName} is running (lightweight gateway)" + else + echo "❌ ${serviceName} is not running" + docker logs --tail 50 ${containerName} 2>/dev/null || true + exit 1 + fi + ' + """ + return + } + + sh """ + ssh ${sshOpts} root@${env.DEPLOY_HOST} ' + STATUS=\$(docker inspect --format="{{.State.Health.Status}}" ${containerName} 2>/dev/null || echo "not_found") + + case "\$STATUS" in + healthy) + echo "✅ ${serviceName} is healthy" + ;; + not_found) + echo "⚠️ ${serviceName} not found (may not be deployed)" + exit 1 + ;; + *) + echo "❌ ${serviceName} is \$STATUS" + echo "=== Last 50 lines of logs ===" + docker logs --tail 50 ${containerName} + exit 1 + ;; + esac + ' + """ +} + +// 按依赖顺序排序服务 +@NonCPS +def sortServicesByDependency(def services) { + def deployOrder = [ + 'viewsh-gateway', + 'viewsh-module-system-server', + 'viewsh-module-infra-server', + 'viewsh-module-iot-server', + 'viewsh-module-iot-gateway' + ] + + return services.sort { a, b -> + deployOrder.indexOf(a) <=> deployOrder.indexOf(b) + } +} + +// 获取服务对应的容器名称 +@NonCPS +def getContainerNameForService(String service) { + def map = [ + 'viewsh-gateway': 'aiot-gateway', + 'viewsh-module-system-server': 'aiot-system-server', + 'viewsh-module-infra-server': 'aiot-infra-server', + 'viewsh-module-iot-server': 'aiot-iot-server', + 'viewsh-module-iot-gateway': 'aiot-iot-gateway' + ] + return map.get(service, "aiot-${service}") +} + +// 获取服务对应的模块路径 +@NonCPS +def getModulePathForService(String service) { + def map = [ + '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' + ] + return map.get(service, service) +} diff --git a/docker-compose.core.yml b/docker-compose.core.yml new file mode 100644 index 0000000..93e23b5 --- /dev/null +++ b/docker-compose.core.yml @@ -0,0 +1,210 @@ +version: '3.8' + +networks: + default: + name: 1panel-network + external: true + +volumes: + app-logs: + +services: + viewsh-gateway: + image: ${REGISTRY_HOST:-localhost:5000}/viewsh-gateway:${IMAGE_TAG:-latest} + container_name: aiot-gateway + restart: on-failure:5 + ports: + - "48080:48080" + environment: + # ===== 基础配置 ===== + TZ: Asia/Shanghai + SPRING_PROFILES_ACTIVE: prod + + # ===== JVM 配置 ===== + JAVA_OPTS: "-Xms512m -Xmx1024m -XX:+UseContainerSupport -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs" + + # ===== Nacos 配置 ===== + NACOS_USERNAME: nacos + NACOS_PASSWORD: 9oDxX~}e7DeP + NACOS_ADDR: 172.17.16.14:8848 + NACOS_DISCOVERY_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + NACOS_CONFIG_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + + # ===== Redis 配置 ===== + SPRING_DATA_REDIS_HOST: 172.17.16.14 + SPRING_DATA_REDIS_PORT: 6379 + SPRING_DATA_REDIS_PASSWORD: "9kHXcZ1ojFsD" + + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: 1536m + cpus: '1.0' + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:48080/actuator/health"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 120s + + viewsh-module-system-server: + image: ${REGISTRY_HOST:-localhost:5000}/viewsh-module-system-server:${IMAGE_TAG:-latest} + container_name: aiot-system-server + restart: on-failure:5 + ports: + - "48081:48081" + environment: + TZ: Asia/Shanghai + SPRING_PROFILES_ACTIVE: prod + JAVA_OPTS: "-Xms512m -Xmx1024m -XX:+UseContainerSupport -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs" + + # ===== Nacos 配置 ===== + NACOS_USERNAME: nacos + NACOS_PASSWORD: 9oDxX~}e7DeP + NACOS_ADDR: 172.17.16.14:8848 + NACOS_DISCOVERY_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + NACOS_CONFIG_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + + # 数据库 + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://172.17.16.14:3306/aiot-platform?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root # TODO: 填入数据库用户名 + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: "65p^VTPi9Qd+" # TODO: 填入数据库密码 + + # Redis + SPRING_DATA_REDIS_HOST: 172.17.16.14 + SPRING_DATA_REDIS_PASSWORD: "9kHXcZ1ojFsD" # TODO: 填入 Redis 密码 + + # 微信配置 (解决 appid 不能为 null) + WX_MP_APP_ID: "wx5b23ba7a5589ecbb" + WX_MP_SECRET: "2a7b3b20c537e52e74afd395eb85f61f" + WX_MINIAPP_APPID: "wxc4598c446f8a9cb3" + WX_MINIAPP_SECRET: "4a1a04e07f6a4a0751b39c3064a92c8b" + + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: 1536m + cpus: '1.0' + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:48081/actuator/health"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 120s + depends_on: + viewsh-gateway: + condition: service_healthy + + viewsh-module-infra-server: + image: ${REGISTRY_HOST:-localhost:5000}/viewsh-module-infra-server:${IMAGE_TAG:-latest} + container_name: aiot-infra-server + restart: on-failure:5 + ports: + - "48082:48082" + environment: + TZ: Asia/Shanghai + SPRING_PROFILES_ACTIVE: prod + JAVA_OPTS: "-Xms512m -Xmx1024m -XX:+UseContainerSupport -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs" + + NACOS_USERNAME: nacos + NACOS_PASSWORD: 9oDxX~}e7DeP + NACOS_ADDR: 172.17.16.14:8848 + NACOS_DISCOVERY_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + NACOS_CONFIG_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://172.17.16.14:3306/aiot-platform?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: "65p^VTPi9Qd+" + + SPRING_DATA_REDIS_HOST: 172.17.16.14 + SPRING_DATA_REDIS_PASSWORD: "9kHXcZ1ojFsD" + + volumes: + - app-logs:/app/logs + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:48082/actuator/health"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 120s + depends_on: + viewsh-module-system-server: + condition: service_healthy + + viewsh-module-iot-server: + image: ${REGISTRY_HOST:-localhost:5000}/viewsh-module-iot-server:${IMAGE_TAG:-latest} + container_name: aiot-iot-server + restart: on-failure:5 + ports: + - "48091:48091" + environment: + TZ: Asia/Shanghai + SPRING_PROFILES_ACTIVE: prod + JAVA_OPTS: "-Xms1024m -Xmx2048m -XX:+UseContainerSupport -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs" + + NACOS_USERNAME: nacos + NACOS_PASSWORD: 9oDxX~}e7DeP + NACOS_ADDR: 172.17.16.14:8848 + NACOS_DISCOVERY_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + NACOS_CONFIG_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://172.17.16.14:3306/aiot-platform?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: "65p^VTPi9Qd+" + + SPRING_DATA_REDIS_HOST: 172.17.16.14 + SPRING_DATA_REDIS_PASSWORD: "9kHXcZ1ojFsD" + + ROCKETMQ_NAME_SERVER: 172.17.16.14:9876 + + # TDengine + TDENGINE_HOST: 172.17.16.14 + TDENGINE_PORT: 6041 + TDENGINE_USERNAME: root + TDENGINE_PASSWORD: taosdata + + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: 2560m + cpus: '1.5' + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:48091/actuator/health"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 120s + depends_on: + viewsh-module-infra-server: + condition: service_healthy + + viewsh-module-iot-gateway: + image: ${REGISTRY_HOST:-localhost:5000}/viewsh-module-iot-gateway:${IMAGE_TAG:-latest} + container_name: aiot-iot-gateway + restart: on-failure:5 + environment: + TZ: Asia/Shanghai + SPRING_PROFILES_ACTIVE: prod + JAVA_OPTS: "-Xms1024m -Xmx2048m -XX:+UseContainerSupport -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs" + + SPRING_DATA_REDIS_HOST: 172.17.16.14 + SPRING_DATA_REDIS_PASSWORD: "9kHXcZ1ojFsD" + + ROCKETMQ_NAME_SERVER: 172.17.16.14:9876 + + volumes: + - app-logs:/app/logs + deploy: + resources: + limits: + memory: 2560m + cpus: '1.5' + depends_on: + - viewsh-module-iot-server + diff --git a/docker/Dockerfile.deps b/docker/Dockerfile.deps new file mode 100644 index 0000000..a8bacbf --- /dev/null +++ b/docker/Dockerfile.deps @@ -0,0 +1,38 @@ +# ============================================ +# Maven 依赖基础镜像 +# 预下载所有依赖,供服务构建时复用 +# ============================================ + +FROM eclipse-temurin:17-jdk-alpine + +# 安装 Maven +RUN apk add --no-cache maven + +WORKDIR /build + +# 复制所有 pom 文件 +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-server/pom.xml viewsh-server/ +COPY viewsh-module-system/pom.xml viewsh-module-system/ +COPY viewsh-module-system/viewsh-module-system-api/pom.xml viewsh-module-system/viewsh-module-system-api/ +COPY viewsh-module-system/viewsh-module-system-server/pom.xml viewsh-module-system/viewsh-module-system-server/ +COPY viewsh-module-infra/pom.xml viewsh-module-infra/ +COPY viewsh-module-infra/viewsh-module-infra-api/pom.xml viewsh-module-infra/viewsh-module-infra-api/ +COPY viewsh-module-infra/viewsh-module-infra-server/pom.xml viewsh-module-infra/viewsh-module-infra-server/ +COPY viewsh-module-iot/pom.xml viewsh-module-iot/ +COPY viewsh-module-iot/viewsh-module-iot-core/pom.xml viewsh-module-iot/viewsh-module-iot-core/ +COPY viewsh-module-iot/viewsh-module-iot-api/pom.xml viewsh-module-iot/viewsh-module-iot-api/ +COPY viewsh-module-iot/viewsh-module-iot-server/pom.xml viewsh-module-iot/viewsh-module-iot-server/ +COPY viewsh-module-iot/viewsh-module-iot-gateway/pom.xml viewsh-module-iot/viewsh-module-iot-gateway/ + +# 下载所有依赖到本地仓库 +RUN mvn dependency:go-offline -B || true + +# 复制源代码 +COPY . . + +# 预编译 framework 和 dependencies(所有服务共享) +RUN mvn install -pl viewsh-dependencies,viewsh-framework -am -DskipTests -B -q || true diff --git a/docker/Dockerfile.service b/docker/Dockerfile.service new file mode 100644 index 0000000..8919447 --- /dev/null +++ b/docker/Dockerfile.service @@ -0,0 +1,78 @@ +# ============================================ +# 服务构建 Dockerfile +# 基于依赖基础镜像,快速编译服务 +# ============================================ + +# 构建参数 +ARG DEPS_IMAGE=aiot-deps:latest + +# ============ 构建阶段 ============ +FROM ${DEPS_IMAGE} AS builder + +ARG MODULE_NAME +ARG JAR_NAME +ARG SKIP_TESTS=true + +WORKDIR /build + +# 复制最新源代码(覆盖基础镜像中的代码) +COPY . . + +# 构建应用 +RUN echo "========================================" && \ + echo "🔨 Building module: ${MODULE_NAME}" && \ + echo "📦 JAR name: ${JAR_NAME}" && \ + echo "⏭️ Skip tests: ${SKIP_TESTS}" && \ + echo "========================================" && \ + mvn package -pl ${MODULE_NAME} -am \ + -DskipTests=${SKIP_TESTS} \ + -B \ + --no-transfer-progress && \ + echo "✅ Build completed successfully!" && \ + ls -lh ${MODULE_NAME}/target/*.jar + +# ============ 运行阶段 ============ +FROM eclipse-temurin:17-jre-alpine + +# 安装必要工具 +RUN apk add --no-cache wget curl + +# 构建参数 +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" \ + SPRING_PROFILES_ACTIVE=prod + +# 暴露端口 +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} -jar app.jar"] diff --git a/docker/Dockerfile.template b/docker/Dockerfile.template new file mode 100644 index 0000000..d358688 --- /dev/null +++ b/docker/Dockerfile.template @@ -0,0 +1,93 @@ +# syntax=docker/dockerfile:1.4 + +# ============================================ +# 多阶段构建:Maven 编译 + 运行时镜像 +# 使用 Docker 层缓存加速构建 +# ============================================ + +# ============ 构建阶段 ============ +FROM eclipse-temurin:17-jdk-alpine AS builder + +# 安装 Maven +RUN apk add --no-cache maven + +WORKDIR /build + +# 构建参数 +ARG MODULE_NAME +ARG JAR_NAME +ARG SKIP_TESTS=true + +# 复制 pom 文件(利用 Docker 层缓存) +# 先复制 pom,再下载依赖,这样依赖层可以被缓存 +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-system/viewsh-module-system-api/pom.xml viewsh-module-system/viewsh-module-system-api/ +COPY viewsh-module-system/viewsh-module-system-server/pom.xml viewsh-module-system/viewsh-module-system-server/ +COPY viewsh-module-infra/pom.xml viewsh-module-infra/ +COPY viewsh-module-infra/viewsh-module-infra-api/pom.xml viewsh-module-infra/viewsh-module-infra-api/ +COPY viewsh-module-infra/viewsh-module-infra-server/pom.xml viewsh-module-infra/viewsh-module-infra-server/ +COPY viewsh-module-iot/pom.xml viewsh-module-iot/ +COPY viewsh-module-iot/viewsh-module-iot-core/pom.xml viewsh-module-iot/viewsh-module-iot-core/ +COPY viewsh-module-iot/viewsh-module-iot-api/pom.xml viewsh-module-iot/viewsh-module-iot-api/ +COPY viewsh-module-iot/viewsh-module-iot-server/pom.xml viewsh-module-iot/viewsh-module-iot-server/ +COPY viewsh-module-iot/viewsh-module-iot-gateway/pom.xml viewsh-module-iot/viewsh-module-iot-gateway/ + +# 下载依赖(这一层会被缓存,除非 pom 文件变化) +RUN mvn dependency:go-offline -B -q || true + +# 复制源代码(代码变化不会影响依赖层的缓存) +COPY . . + +# 编译打包 +RUN mvn clean package -pl ${MODULE_NAME} -am -DskipTests=${SKIP_TESTS} -B -q + +# ============ 运行阶段 ============ +FROM eclipse-temurin:17-jre-alpine + +# 安装必要工具(用于健康检查) +RUN apk add --no-cache wget curl + +# 构建参数 +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/docs/deployment-guide.md b/docs/deployment-guide.md new file mode 100644 index 0000000..6a0db61 --- /dev/null +++ b/docs/deployment-guide.md @@ -0,0 +1,376 @@ +# AIOT Platform Cloud 部署方案说明 + +本文档说明 AIOT Platform Cloud 的部署架构、CI/CD 流程和关键配置。 + +## 系统架构 + +### 服务列表 + +| 服务名称 | 容器名称 | 端口 | 说明 | +|---------|---------|------|------| +| viewsh-gateway | aiot-gateway | 48080 | API 网关(统一入口) | +| viewsh-module-system-server | aiot-system-server | 48081 | 系统管理服务 | +| viewsh-module-infra-server | aiot-infra-server | 48082 | 基础设施服务 | +| viewsh-module-iot-server | aiot-iot-server | 48091 | IoT 核心服务 | +| viewsh-module-iot-gateway | aiot-iot-gateway | - | IoT 设备网关(内部服务) | + +### 技术栈 + +- **Java**: 17 +- **Spring Boot**: 3.5.9 +- **构建工具**: Maven 3.8+ +- **容器**: Docker 20.10+, Docker Compose 2.20+ +- **CI/CD**: Jenkins 2.400+ + +### 依赖服务 + +| 服务 | 地址 | 端口 | 用途 | +|-----|------|------|------| +| Nacos | 172.17.16.14 | 8848 | 服务发现、配置中心 | +| MySQL | 172.17.16.14 | 3306 | 数据库 | +| Redis | 172.17.16.14 | 6379 | 缓存 | +| RocketMQ | 172.17.16.14 | 9876 | 消息队列 | +| TDengine | 172.17.16.14 | 6041 | 时序数据库 | +| Docker Registry | localhost | 5000 | 镜像仓库 | + +## CI/CD 方案 + +### Jenkins Pipeline 工作流 + +``` +代码提交 → 变更检测 → 构建依赖镜像 → 并行构建服务 → 推送镜像 → 按序部署 → 健康检查 +``` + +**配置文件**: `Jenkinsfile` + +**核心特性**: + +1. **智能构建** + - 检测变更文件,只构建受影响的服务 + - Maven 依赖层缓存,避免重复下载 + - 动态并行构建(根据 CPU 和内存自动调整并行度) + +2. **部署策略** + - 依赖顺序部署:gateway → system → infra → iot-server → iot-gateway + - 部署前自动备份当前版本 + - 健康检查失败自动回滚 + +3. **性能监控** + - 阶段耗时统计 + - 自动生成性能报告 + - 系统资源检测 + +**关键配置**: + +```groovy +REGISTRY = 'localhost:5000' // 镜像仓库 +DEPLOY_HOST = '172.19.0.1' // 部署目标服务器 +DEPLOY_PATH = '/opt/aiot-platform-cloud' // 部署目录 +CORE_SERVICES = 'gateway,system,infra,iot-server,iot-gateway' +``` + +## Docker 部署方案 + +### 镜像构建 + +**多阶段构建** (`docker/Dockerfile.template`): + +```dockerfile +Stage 1: 构建阶段 (eclipse-temurin:17-jdk-alpine) + - Maven 编译打包 + - 利用 Docker 层缓存加速依赖下载 + +Stage 2: 运行阶段 (eclipse-temurin:17-jre-alpine) + - 复制 JAR 文件 + - 非 root 用户运行 + - 内置健康检查 +``` + +**优化点**: +- 依赖缓存层(pom.xml 先于源码复制) +- 最小化运行时镜像(JRE 替代 JDK) +- 安全性(非 root 用户) + +### 容器编排 + +**配置文件**: `docker-compose.core.yml` + +**网络配置**: +```yaml +networks: + default: + name: 1panel-network + external: true +``` + +**资源限制**: + +| 服务 | 内存限制 | CPU 限制 | +|-----|---------|---------| +| gateway | 1536m | 1.0 | +| system | 1536m | 1.0 | +| infra | 1536m | 1.0 | +| iot-server | 2560m | 1.5 | +| iot-gateway | 2560m | 1.5 | + +**健康检查**: +```yaml +healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:48080/actuator/health"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 120s +``` + +### 环境配置 + +通过环境变量注入配置,支持动态覆盖: + +```yaml +environment: + # Spring Profile + SPRING_PROFILES_ACTIVE: prod + + # JVM 参数 + JAVA_OPTS: "-Xms512m -Xmx1024m ..." + + # Nacos 配置 + NACOS_ADDR: 172.17.16.14:8848 + NACOS_NAMESPACE: "8efd6d96-de7f-4664-b28e-c2788ffa1395" + + # 数据库 + SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://... + + # Redis + SPRING_DATA_REDIS_HOST: 172.17.16.14 +``` + +## 服务发现与配置 + +### Nacos 集成 + +所有服务通过 Nacos 实现服务发现和配置管理: + +**命名空间**: `8efd6d96-de7f-4664-b28e-c2788ffa1395` + +**配置文件命名规范**: `{服务名}-{profile}.yaml` + +示例: +- `gateway-server-prod.yaml` +- `system-server-prod.yaml` +- `iot-server-prod.yaml` + +**配置加载顺序**: +1. 本地配置 `application-local.yaml` +2. Nacos 配置(覆盖本地配置) + +## 部署流程 + +### 自动部署(推荐) + +```bash +git push origin master +# Jenkins 自动触发构建和部署 +``` + +### 手动部署 + +适用于紧急部署或 Jenkins 不可用的场景。 + +#### 前置准备 + +**1. 确保依赖服务可用** + +```bash +# 检查 Nacos +curl http://172.17.16.14:8848/nacos/ + +# 检查 MySQL +mysql -h 172.17.16.14 -u root -p -e "SELECT 1" + +# 检查 Redis +redis-cli -h 172.17.16.14 -a PING +``` + +**2. 准备部署环境** + +```bash +# 创建部署目录 +mkdir -p /opt/aiot-platform-cloud +cd /opt/aiot-platform-cloud + +# 创建 Docker 网络(如果不存在) +docker network create 1panel-network + +# 创建日志卷 +docker volume create app-logs +``` + +**3. 上传配置文件** + +将 `docker-compose.core.yml` 上传到 `/opt/aiot-platform-cloud/` 目录。 + +根据实际环境修改配置: +- 镜像仓库地址 `REGISTRY_HOST` +- Nacos 地址和命名空间 +- 数据库连接信息 +- Redis 连接信息 + +#### 构建镜像(可选) + +如果镜像仓库中已有镜像,可跳过此步骤。 + +```bash +# 构建依赖镜像(首次构建或 pom.xml 变更时) +docker build -f docker/Dockerfile.deps -t localhost:5000/aiot-deps:latest . + +# 构建服务镜像 +docker build \ + -f docker/Dockerfile.service \ + --build-arg MODULE_NAME=viewsh-gateway \ + --build-arg JAR_NAME=viewsh-gateway \ + -t localhost:5000/viewsh-gateway:latest \ + . + +# 推送到镜像仓库 +docker push localhost:5000/viewsh-gateway:latest +``` + +#### 部署服务 + +**1. 拉取镜像** + +```bash +docker compose -f docker-compose.core.yml pull +``` + +**2. 启动服务** + +```bash +# 启动所有服务 +docker compose -f docker-compose.core.yml up -d + +# 或按依赖顺序逐个启动(推荐用于故障排查) +docker compose -f docker-compose.core.yml up -d viewsh-gateway +docker compose -f docker-compose.core.yml up -d viewsh-module-system-server +docker compose -f docker-compose.core.yml up -d viewsh-module-infra-server +docker compose -f docker-compose.core.yml up -d viewsh-module-iot-server +docker compose -f docker-compose.core.yml up -d viewsh-module-iot-gateway +``` + +**3. 查看启动状态** + +```bash +# 查看容器状态 +docker compose -f docker-compose.core.yml ps + +# 查看服务日志 +docker compose -f docker-compose.core.yml logs -f + +# 查看特定服务日志 +docker logs -f aiot-gateway +``` + +#### 更新服务 + +更新已有服务: + +```bash +# 拉取新镜像 +docker compose -f docker-compose.core.yml pull + +# 重启服务(保持配置不变) +docker compose -f docker-compose.core.yml up -d + +# 或重启特定服务 +docker compose -f docker-compose.core.yml up -d viewsh-module-iot-server +``` + +#### 回滚服务 + +如果新版本出现问题: + +```bash +# 1. 查看可用镜像版本 +docker images | grep viewsh + +# 2. 修改 docker-compose.core.yml 中的 IMAGE_TAG +# 或者通过环境变量指定 +export IMAGE_TAG= + +# 3. 重新拉取并启动 +docker compose -f docker-compose.core.yml pull +docker compose -f docker-compose.core.yml up -d +``` + +### 验证部署 + +```bash +# 检查容器状态 +docker compose -f docker-compose.core.yml ps + +# 检查健康状态 +docker inspect --format='{{.State.Health.Status}}' aiot-gateway + +# 访问 API +curl http://:48080/actuator/health +``` + +## 关键设计决策 + +### 1. 为什么要用 Docker 多阶段构建? + +- **构建阶段**: 需要 JDK + Maven(体积大) +- **运行阶段**: 只需 JRE(体积小) +- **结果**: 镜像从 500MB+ 降至 200MB 左右 + +### 2. 为什么要智能构建检测? + +- 只构建变更的服务,节省时间 +- Maven 依赖缓存,避免重复下载 +- 并行构建,提升效率 + +**对比**: +- 全量构建:~15 分钟 +- 智能构建:~5 分钟(单服务变更) + +### 3. 为什么要按依赖顺序部署? + +服务间存在依赖关系: +- gateway 需要所有后端服务先启动 +- iot-server 依赖 system 和 infra +- iot-gateway 依赖 iot-server + +### 4. 为什么要健康检查和自动回滚? + +- 保证部署失败时服务可用 +- 减少故障恢复时间 +- 提高系统可靠性 + +### 5. IoT Gateway 为什么没有健康检查? + +IoT Gateway 是轻量级设备网关,不暴露 HTTP 端点,只检查容器运行状态。 + +## 目录结构 + +``` +aiot-platform-cloud/ +├── Jenkinsfile # CI/CD 流程定义 +├── docker-compose.core.yml # 服务编排配置 +├── docker/ +│ ├── Dockerfile.template # 通用镜像模板 +│ ├── Dockerfile.deps # Maven 依赖镜像 +│ └── Dockerfile.service # 服务构建镜像 +├── viewsh-gateway/ # 网关服务 +├── viewsh-module-system/ # 系统服务 +├── viewsh-module-infra/ # 基础设施服务 +└── viewsh-module-iot/ # IoT 服务 + ├── viewsh-module-iot-server/ # IoT 核心服务 + └── viewsh-module-iot-gateway/ # IoT 设备网关 +``` + +## 相关文档 + +- [Jenkinsfile](../Jenkinsfile) - Jenkins Pipeline 完整定义 +- [docker-compose.core.yml](../docker-compose.core.yml) - Docker Compose 配置 \ No newline at end of file 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 "=========================================" diff --git a/viewsh-framework/viewsh-spring-boot-starter-monitor/pom.xml b/viewsh-framework/viewsh-spring-boot-starter-monitor/pom.xml index b524c50..22e7562 100644 --- a/viewsh-framework/viewsh-spring-boot-starter-monitor/pom.xml +++ b/viewsh-framework/viewsh-spring-boot-starter-monitor/pom.xml @@ -72,7 +72,7 @@ de.codecentric spring-boot-admin-starter-client - true + diff --git a/viewsh-gateway/src/main/resources/application-prod.yaml b/viewsh-gateway/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..82d1fcd --- /dev/null +++ b/viewsh-gateway/src/main/resources/application-prod.yaml @@ -0,0 +1,75 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: ${NACOS_ADDR:127.0.0.1:8848} + username: ${NACOS_USERNAME:nacos} + password: ${NACOS_PASSWORD:nacos} + discovery: + namespace: ${NACOS_DISCOVERY_NAMESPACE:prod} + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + config: + namespace: ${NACOS_CONFIG_NAMESPACE:prod} + group: DEFAULT_GROUP + file-extension: yaml + refresh-enabled: true + +--- #################### Redis 配置 #################### +spring: + data: + redis: + host: 127.0.0.1 + port: 6379 + database: 0 + password: "" + timeout: 5000ms + lettuce: + pool: + max-active: 8 + max-wait: -1ms + max-idle: 8 + min-idle: 0 + +--- #################### 服务保障相关配置 #################### + +lock4j: + acquire-timeout: 3000 + expire: 30000 + +--- #################### 监控相关配置 #################### + +management: + endpoints: + web: + base-path: /actuator + exposure: + include: '*' + +spring: + boot: + admin: + client: + instance: + service-host-type: IP + username: ${SPRING_BOOT_ADMIN_USERNAME:admin} + password: ${SPRING_BOOT_ADMIN_PASSWORD:admin} + +logging: + level: + root: INFO + com.viewsh: ${LOG_LEVEL:INFO} + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR + file: + name: ${LOG_FILE_PATH:/app/logs}/${spring.application.name}.log + +--- #################### 芋道相关配置 #################### + +viewsh: + demo: false + env: + tag: ${HOSTNAME:prod} + security: + mock-enable: false diff --git a/viewsh-module-infra/viewsh-module-infra-server/pom.xml b/viewsh-module-infra/viewsh-module-infra-server/pom.xml index f99ec40..f076898 100644 --- a/viewsh-module-infra/viewsh-module-infra-server/pom.xml +++ b/viewsh-module-infra/viewsh-module-infra-server/pom.xml @@ -118,10 +118,10 @@ viewsh-spring-boot-starter-monitor - - - - + + de.codecentric + spring-boot-admin-starter-server + diff --git a/viewsh-module-infra/viewsh-module-infra-server/src/main/resources/application-prod.yaml b/viewsh-module-infra/viewsh-module-infra-server/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..412ae51 --- /dev/null +++ b/viewsh-module-infra/viewsh-module-infra-server/src/main/resources/application-prod.yaml @@ -0,0 +1,166 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: ${NACOS_ADDR:127.0.0.1:8848} + username: ${NACOS_USERNAME:nacos} + password: ${NACOS_PASSWORD:nacos} + discovery: + namespace: ${NACOS_DISCOVERY_NAMESPACE:prod} + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + config: + namespace: ${NACOS_CONFIG_NAMESPACE:prod} + group: DEFAULT_GROUP + file-extension: yaml + refresh-enabled: true + lifecycle: + timeout-per-shutdown-phase: 30s # 增加优雅关闭超时时间 + +--- #################### 数据库相关配置 #################### +spring: + datasource: + druid: + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + url-pattern: /druid/* + login-username: ${DRUID_USERNAME:admin} + login-password: ${DRUID_PASSWORD:admin} + filter: + stat: + enabled: true + log-slow-sql: true + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: + druid: + initial-size: 5 + min-idle: 10 + max-active: 20 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 600000 + max-evictable-idle-time-millis: 1800000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + primary: master + datasource: + master: + url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:aiot_platform}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: ${MYSQL_USER:root} + password: ${MYSQL_PASSWORD:} + slave: + lazy: true + url: jdbc:mysql://${MYSQL_SLAVE_HOST:${MYSQL_HOST:127.0.0.1}}:${MYSQL_SLAVE_PORT:${MYSQL_PORT:3306}}/${MYSQL_DATABASE:aiot_platform}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: ${MYSQL_SLAVE_USER:${MYSQL_USER:root}} + password: ${MYSQL_SLAVE_PASSWORD:${MYSQL_PASSWORD:}} + + data: + redis: + host: ${REDIS_HOST:127.0.0.1} + port: ${REDIS_PORT:6379} + database: ${REDIS_DATABASE:0} + password: ${REDIS_PASSWORD:} + timeout: 5000ms + lettuce: + pool: + max-active: 8 + max-wait: -1ms + max-idle: 8 + min-idle: 0 + +--- #################### MQ 消息队列相关配置 #################### + +rocketmq: + name-server: ${ROCKETMQ_NAMESRV_ADDR:127.0.0.1:9876} + +spring: + rabbitmq: + host: ${RABBITMQ_HOST:127.0.0.1} + port: ${RABBITMQ_PORT:5672} + username: ${RABBITMQ_USERNAME:guest} + password: ${RABBITMQ_PASSWORD:guest} + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:127.0.0.1:9092} + +--- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: ${XXL_JOB_ADMIN_ADDRESSES:http://172.17.16.14:19090/xxl-job-admin} + +--- #################### 服务保障相关配置 #################### + +lock4j: + acquire-timeout: 3000 + expire: 30000 + +--- #################### 监控相关配置 #################### + +management: + endpoints: + web: + base-path: /actuator + exposure: + include: '*' + +spring: + boot: + admin: + client: + instance: + service-host-type: IP + username: ${SPRING_BOOT_ADMIN_USERNAME:admin} + password: ${SPRING_BOOT_ADMIN_PASSWORD:admin} + context-path: /admin # 配置 Spring + +logging: + level: + root: INFO + com.viewsh: ${LOG_LEVEL:INFO} + com.viewsh.module.infra.dal.mysql: debug + com.viewsh.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO + com.viewsh.module.infra.dal.mysql.file.FileConfigMapper: INFO + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR + file: + name: ${LOG_FILE_PATH:/app/logs}/${spring.application.name}.log + +--- #################### 芋道相关配置 #################### + +viewsh: + demo: false + env: + tag: ${HOSTNAME:prod} + captcha: + enable: true + security: + mock-enable: false + +--- #################### Quartz 优雅关闭配置 #################### + +spring: + quartz: + properties: + org: + quartz: + scheduler: + makeSchedulerThreadDaemon: true + shutdownHook: clean_shutdown + threadPool: + threadNamePrefix: QuartzSchedulerThread + threadsInheritContextClassLoaderOfInitializingThread: true + plugin: + shutdownhook: + class: org.quartz.plugins.management.ShutdownHookPlugin + cleanShutdown: true diff --git a/viewsh-module-iot/viewsh-module-iot-gateway/pom.xml b/viewsh-module-iot/viewsh-module-iot-gateway/pom.xml index 39eccd5..953216d 100644 --- a/viewsh-module-iot/viewsh-module-iot-gateway/pom.xml +++ b/viewsh-module-iot/viewsh-module-iot-gateway/pom.xml @@ -23,6 +23,7 @@ ${revision} + org.springframework spring-web diff --git a/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application-prod.yaml b/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..3e3e87d --- /dev/null +++ b/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application-prod.yaml @@ -0,0 +1,132 @@ +--- #################### 应用配置 #################### + +spring: + application: + name: iot-gateway-server + profiles: + active: prod + + # Redis 配置 + data: + redis: + host: ${REDIS_HOST:127.0.0.1} + port: ${REDIS_PORT:6379} + database: ${REDIS_DATABASE:0} + password: ${REDIS_PASSWORD:} + timeout: ${REDIS_TIMEOUT:30000ms} + +--- #################### 消息队列相关 #################### + +# rocketmq 配置项,对应 RocketMQProperties 配置类 +rocketmq: + name-server: ${ROCKETMQ_NAMESRV_ADDR:127.0.0.1:9876} + # Producer 配置项 + producer: + group: ${spring.application.name}_PRODUCER + +--- #################### IoT 网关相关配置 #################### + +viewsh: + iot: + # 消息总线配置 + message-bus: + type: ${IOT_MESSAGE_BUS_TYPE:redis} + + # 网关配置 + gateway: + # 设备 RPC 配置 + rpc: + url: ${IOT_RPC_URL:http://127.0.0.1:48091} + connect-timeout: ${IOT_RPC_CONNECT_TIMEOUT:30s} + read-timeout: ${IOT_RPC_READ_TIMEOUT:30s} + # 设备 Token 配置 + token: + secret: ${IOT_TOKEN_SECRET:viewshIotGatewayTokenSecret123456789} + expiration: ${IOT_TOKEN_EXPIRATION:7d} + + # 协议配置 + protocol: + # ==================================== + # 针对引入的 HTTP 组件的配置 + # ==================================== + http: + enabled: ${IOT_HTTP_ENABLED:true} + server-port: ${IOT_HTTP_PORT:8092} + # ==================================== + # 针对引入的 EMQX 组件的配置 + # ==================================== + emqx: + enabled: ${IOT_EMQX_ENABLED:false} + http-port: ${IOT_EMQX_HTTP_PORT:8090} + mqtt-host: ${IOT_EMQX_MQTT_HOST:127.0.0.1} + mqtt-port: ${IOT_EMQX_MQTT_PORT:1883} + mqtt-username: ${IOT_EMQX_MQTT_USERNAME:admin} + mqtt-password: ${IOT_EMQX_MQTT_PASSWORD:public} + mqtt-client-id: ${IOT_EMQX_MQTT_CLIENT_ID:iot-gateway-mqtt} + mqtt-ssl: ${IOT_EMQX_MQTT_SSL:false} + mqtt-topics: + - "/sys/#" + clean-session: true + keep-alive-interval-seconds: 60 + max-inflight-queue: 10000 + connect-timeout-seconds: 10 + # 是否信任所有 SSL 证书 (默认: false)。警告:生产环境必须为 false! + # 仅在开发环境或内网测试时,如果使用了自签名证书,可以临时设置为 true + trust-all: ${IOT_EMQX_TRUST_ALL:false} + # 遗嘱消息配置 (用于网关异常下线时通知其他系统) + will: + enabled: ${IOT_EMQX_WILL_ENABLED:true} + topic: "gateway/status/${viewsh.iot.gateway.emqx.mqtt-client-id}" + payload: "offline" + qos: 1 + retain: true + # 高级 SSL/TLS 配置 (当 trust-all: false 且 mqtt-ssl: true 时生效) + ssl-options: + key-store-path: ${IOT_EMQX_SSL_KEYSTORE_PATH:classpath:certs/client.jks} + key-store-password: ${IOT_EMQX_SSL_KEYSTORE_PASSWORD:your-keystore-password} + trust-store-path: ${IOT_EMQX_SSL_TRUSTSTORE_PATH:classpath:certs/trust.jks} + trust-store-password: ${IOT_EMQX_SSL_TRUSTSTORE_PASSWORD:your-truststore-password} + # ==================================== + # 针对引入的 TCP 组件的配置 + # ==================================== + tcp: + enabled: ${IOT_TCP_ENABLED:false} + port: ${IOT_TCP_PORT:8091} + keep-alive-timeout-ms: ${IOT_TCP_KEEPALIVE_TIMEOUT:30000} + max-connections: ${IOT_TCP_MAX_CONNECTIONS:1000} + ssl-enabled: ${IOT_TCP_SSL_ENABLED:false} + ssl-cert-path: ${IOT_TCP_SSL_CERT_PATH:classpath:certs/client.jks} + ssl-key-path: ${IOT_TCP_SSL_KEY_PATH:classpath:certs/client.jks} + # ==================================== + # 针对引入的 MQTT 组件的配置 + # ==================================== + mqtt: + enabled: ${IOT_MQTT_ENABLED:true} + port: ${IOT_MQTT_PORT:1883} + max-message-size: ${IOT_MQTT_MAX_MESSAGE_SIZE:8192} + connect-timeout-seconds: ${IOT_MQTT_CONNECT_TIMEOUT:60} + ssl-enabled: ${IOT_MQTT_SSL_ENABLED:false} + +--- #################### 日志相关配置 #################### + +# 基础日志配置 +logging: + file: + name: ${LOG_FILE_PATH:/app/logs}/${spring.application.name}.log + level: + # 应用基础日志级别 + com.viewsh.module.iot.gateway: ${LOG_LEVEL_IOT_GATEWAY:INFO} + org.springframework.boot: INFO + # RocketMQ 日志 + org.apache.rocketmq: WARN + # MQTT 客户端日志 + # io.vertx.mqtt: DEBUG + # 生产环境日志 + com.viewsh.module.iot.gateway.protocol.emqx: ${LOG_LEVEL_EMQX:INFO} + com.viewsh.module.iot.gateway.protocol.http: ${LOG_LEVEL_HTTP:INFO} + com.viewsh.module.iot.gateway.protocol.mqtt: ${LOG_LEVEL_MQTT:INFO} + # 根日志级别 + root: INFO + +debug: false + diff --git a/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application.yaml b/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application.yaml index 2176f50..9064055 100644 --- a/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application.yaml +++ b/viewsh-module-iot/viewsh-module-iot-gateway/src/main/resources/application.yaml @@ -4,6 +4,11 @@ spring: profiles: active: local # 默认激活本地开发环境 + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 +# - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + # Redis 配置 data: redis: @@ -12,7 +17,8 @@ spring: database: ${REDIS_DATABASE:0} # Redis 数据库索引 # password: ${REDIS_PASSWORD:9kHXcZ1ojFsD} # Redis 密码 timeout: 30000ms # 连接超时时间 - +server: + port: 48095 --- #################### 消息队列相关 #################### # rocketmq 配置项,对应 RocketMQProperties 配置类 diff --git a/viewsh-module-iot/viewsh-module-iot-server/pom.xml b/viewsh-module-iot/viewsh-module-iot-server/pom.xml index 4df64c4..819bb14 100644 --- a/viewsh-module-iot/viewsh-module-iot-server/pom.xml +++ b/viewsh-module-iot/viewsh-module-iot-server/pom.xml @@ -118,6 +118,12 @@ viewsh-spring-boot-starter-test + + + com.viewsh + viewsh-spring-boot-starter-monitor + + com.viewsh diff --git a/viewsh-module-iot/viewsh-module-iot-server/src/main/resources/application-prod.yaml b/viewsh-module-iot/viewsh-module-iot-server/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..ca823dd --- /dev/null +++ b/viewsh-module-iot/viewsh-module-iot-server/src/main/resources/application-prod.yaml @@ -0,0 +1,156 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: ${NACOS_ADDR:127.0.0.1:8848} + username: ${NACOS_USERNAME:nacos} + password: ${NACOS_PASSWORD:nacos} + discovery: + namespace: ${NACOS_DISCOVERY_NAMESPACE:prod} + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + config: + namespace: ${NACOS_CONFIG_NAMESPACE:prod} + group: DEFAULT_GROUP + file-extension: yaml + refresh-enabled: true + +--- #################### 数据库相关配置 #################### +spring: + datasource: + druid: + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + url-pattern: /druid/* + login-username: ${DRUID_USERNAME:admin} + login-password: ${DRUID_PASSWORD:admin} + filter: + stat: + enabled: true + log-slow-sql: true + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: + druid: + initial-size: 5 + min-idle: 10 + max-active: 20 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 600000 + max-evictable-idle-time-millis: 1800000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + primary: master + datasource: + master: + url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:aiot_platform}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: ${MYSQL_USER:root} + password: ${MYSQL_PASSWORD:} + slave: + lazy: true + url: jdbc:mysql://${MYSQL_SLAVE_HOST:${MYSQL_HOST:127.0.0.1}}:${MYSQL_SLAVE_PORT:${MYSQL_PORT:3306}}/${MYSQL_DATABASE:aiot_platform}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: ${MYSQL_SLAVE_USER:${MYSQL_USER:root}} + password: ${MYSQL_SLAVE_PASSWORD:${MYSQL_PASSWORD:}} + tdengine: + url: jdbc:TAOS-RS://${TDENGINE_HOST:172.17.16.14}:${TDENGINE_PORT:6041}/aiot_platform + driver-class-name: com.taosdata.jdbc.rs.RestfulDriver + username: ${TDENGINE_USERNAME:root} + password: ${TDENGINE_PASSWORD:taosdata} + druid: + validation-query: SELECT SERVER_STATUS() + + data: + redis: + host: ${REDIS_HOST:127.0.0.1} + port: ${REDIS_PORT:6379} + database: ${REDIS_DATABASE:0} + password: ${REDIS_PASSWORD:} + timeout: 5000ms + lettuce: + pool: + max-active: 8 + max-wait: -1ms + max-idle: 8 + min-idle: 0 + +--- #################### MQ 消息队列相关配置 #################### + +rocketmq: + name-server: ${ROCKETMQ_NAMESRV_ADDR:127.0.0.1:9876} + +spring: + # 禁用 RabbitMQ 自动配置(如果不需要 RabbitMQ,避免启动时连接失败) + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration + # RabbitMQ 配置(已禁用自动配置,仅保留配置项供业务代码使用) + # rabbitmq: + # host: ${RABBITMQ_HOST:127.0.0.1} + # port: ${RABBITMQ_PORT:5672} + # username: ${RABBITMQ_USERNAME:guest} + # password: ${RABBITMQ_PASSWORD:guest} + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:127.0.0.1:9092} + +--- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: ${XXL_JOB_ADMIN_ADDRESSES:http://172.17.16.14:19090/xxl-job-admin} + +--- #################### 服务保障相关配置 #################### + +lock4j: + acquire-timeout: 3000 + expire: 30000 + +--- #################### 监控相关配置 #################### + +management: + endpoints: + web: + base-path: /actuator + exposure: + include: '*' + +spring: + boot: + admin: + client: + instance: + service-host-type: IP + username: ${SPRING_BOOT_ADMIN_USERNAME:admin} + password: ${SPRING_BOOT_ADMIN_PASSWORD:admin} + +logging: + level: + root: INFO + com.viewsh: ${LOG_LEVEL:INFO} + com.viewsh.module.iot.dal.mysql: debug + com.viewsh.module.iot.dal.mysql.sms.SmsChannelMapper: INFO + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR + file: + name: ${LOG_FILE_PATH:/app/logs}/${spring.application.name}.log + +--- #################### 芋道相关配置 #################### + +viewsh: + demo: false + env: + tag: ${HOSTNAME:prod} + captcha: + enable: true + security: + mock-enable: false diff --git a/viewsh-module-system/viewsh-module-system-server/src/main/resources/application-prod.yaml b/viewsh-module-system/viewsh-module-system-server/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..ccb110e --- /dev/null +++ b/viewsh-module-system/viewsh-module-system-server/src/main/resources/application-prod.yaml @@ -0,0 +1,206 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: ${NACOS_ADDR:127.0.0.1:8848} + username: ${NACOS_USERNAME:nacos} + password: ${NACOS_PASSWORD:nacos} + discovery: + namespace: ${NACOS_DISCOVERY_NAMESPACE:prod} + group: DEFAULT_GROUP + metadata: + version: 1.0.0 + config: + namespace: ${NACOS_CONFIG_NAMESPACE:prod} + group: DEFAULT_GROUP + file-extension: yaml + refresh-enabled: true + +--- #################### 数据库相关配置 #################### +spring: + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: ${DRUID_USERNAME:admin} + login-password: ${DRUID_PASSWORD:admin} + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 + min-idle: 10 + max-active: 20 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 600000 + max-evictable-idle-time-millis: 1800000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + primary: master + datasource: + master: + url: jdbc:mysql://127.0.0.1:3306/aiot_platform?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: root + password: root + slave: # 从库配置(可选) + lazy: true + url: jdbc:mysql://127.0.0.1:3306/aiot_platform?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: root + password: root + + # Redis 配置 + data: + redis: + host: 127.0.0.1 + port: 6379 + database: 0 + password: "" + timeout: 5000ms + lettuce: + pool: + max-active: 8 + max-wait: -1ms + max-idle: 8 + min-idle: 0 + +--- #################### MQ 消息队列相关配置 #################### + +# rocketmq 配置项 +rocketmq: + name-server: ${ROCKETMQ_NAMESRV_ADDR:127.0.0.1:9876} + +spring: + # RabbitMQ 配置项(可选) + rabbitmq: + host: ${RABBITMQ_HOST:127.0.0.1} + port: ${RABBITMQ_PORT:5672} + username: ${RABBITMQ_USERNAME:guest} + password: ${RABBITMQ_PASSWORD:guest} + # Kafka 配置项(可选) + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:127.0.0.1:9092} + +--- #################### 定时任务相关配置 #################### +xxl: + job: + admin: + addresses: ${XXL_JOB_ADMIN_ADDRESSES:http://172.17.16.14:19090/xxl-job-admin} + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 + expire: 30000 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator + exposure: + include: '*' + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + client: + instance: + service-host-type: IP + username: ${SPRING_BOOT_ADMIN_USERNAME:admin} + password: ${SPRING_BOOT_ADMIN_PASSWORD:admin} + +# 日志文件配置 +logging: + level: + root: INFO + com.viewsh: ${LOG_LEVEL:INFO} + file: + name: ${LOG_FILE_PATH:/app/logs}/${spring.application.name}.log + +--- #################### 微信公众号、小程序相关配置 #################### +wx: + mp: # 公众号配置(必填) + app-id: ${WX_MP_APP_ID:wx5b23ba7a5589ecbb} # 优先环境变量,兜底默认值 + secret: ${WX_MP_SECRET:2a7b3b20c537e52e74afd395eb85f61f} + config-storage: + type: RedisTemplate + key-prefix: wx + http-client-type: HttpClient + miniapp: # 小程序配置(必填) + appid: ${WX_MINIAPP_APPID:wxc4598c446f8a9cb3} + secret: ${WX_MINIAPP_SECRET:4a1a04e07f6a4a0751b39c3064a92c8b} + config-storage: + type: RedisTemplate + key-prefix: wa + http-client-type: HttpClient + +--- #################### 芋道相关配置 #################### + +viewsh: + demo: false # 生产环境关闭演示模式 + env: + tag: ${HOSTNAME:prod} + captcha: + enable: true # 生产环境开启验证码 + security: + mock-enable: false # 生产环境关闭 mock + access-log: + enable: true + wxa-code: + env-version: release + wxa-subscribe-message: + miniprogram-state: formal + +justauth: + enabled: true + type: + DINGTALK: # 钉钉 + client-id: dingvrnreaje3yqvzhxg + client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI + ignore-check-redirect-uri: true + WECHAT_ENTERPRISE: # 企业微信 + client-id: wwd411c69a39ad2e54 + client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw + agent-id: 1000004 + ignore-check-redirect-uri: true + # noinspection SpringBootApplicationYaml + WECHAT_MINI_PROGRAM: # 微信小程序 + client-id: ${wx.miniapp.appid} + client-secret: ${wx.miniapp.secret} + ignore-check-redirect-uri: true + ignore-check-state: true # 微信小程序,不会使用到 state,所以不进行校验 + WECHAT_MP: # 微信公众号 + client-id: ${wx.mp.app-id} + client-secret: ${wx.mp.secret} + ignore-check-redirect-uri: true + ALIPAY: # 支付宝小程序 + client-id: xx + client-secret: xx + alipay-public-key: xx + ignore-check-redirect-uri: true + ignore-check-state: true + cache: + type: REDIS + prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: + timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 diff --git a/viewsh-module-system/viewsh-module-system-server/src/main/resources/application.yaml b/viewsh-module-system/viewsh-module-system-server/src/main/resources/application.yaml index 0284eeb..e53f4b8 100644 --- a/viewsh-module-system/viewsh-module-system-server/src/main/resources/application.yaml +++ b/viewsh-module-system/viewsh-module-system-server/src/main/resources/application.yaml @@ -136,7 +136,7 @@ aj: cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存 timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行 type: blockPuzzle # 验证码类型 default 三种都实例化。blockPuzzle 滑块拼图、clickWord 文字点选、pictureWord 文本输入 - water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode + water-mark: AIOT # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode interference-options: 0 # 滑动干扰项(0/1/2) req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false req-get-lock-limit: 5 # 验证失败5次,get接口锁定