From 4c6be2c489a5854e11f16c871b86f311b65d2d5e Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Tue, 16 Jun 2026 19:33:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=89=88=E6=9C=AC=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=9C=8B=E6=9D=BF=20=E2=80=94=20=E6=A3=80=E6=9F=A5=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=97=B6=E5=B1=95=E7=A4=BA=E5=85=A8=E9=87=8F=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=AF=B9=E6=AF=94=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dockerfile.tenant/ops: 注入 SERVICE_VERSION ARG,设置 com.xuqm.version 标签 - Jenkinsfile: 移除手动 IMAGE_TAG;自动递增语义版本;构建后更新 versions.json;commit VERSION 文件 - SecurityCenterView: 点击「检查更新」后始终展示全量服务当前版本 vs 云端版本对比表格(不再仅 hasUpdate 时显示) Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile.ops | 4 + Dockerfile.tenant | 4 + Jenkinsfile | 68 ++++++++++-- .../src/views/security/SecurityCenterView.vue | 100 +++++++++++++----- 4 files changed, 138 insertions(+), 38 deletions(-) diff --git a/Dockerfile.ops b/Dockerfile.ops index d566c5c..668422a 100644 --- a/Dockerfile.ops +++ b/Dockerfile.ops @@ -26,6 +26,10 @@ RUN cd ops-platform && \ FROM --platform=linux/amd64 nginx:1.27-alpine +ARG SERVICE_VERSION=0.0.0 +LABEL com.xuqm.version="${SERVICE_VERSION}" +LABEL com.xuqm.service="ops-web" + COPY nginx/ops.conf /etc/nginx/conf.d/default.conf COPY --from=build /workspace/ops-platform/dist /usr/share/nginx/html diff --git a/Dockerfile.tenant b/Dockerfile.tenant index e432a10..08ecfda 100644 --- a/Dockerfile.tenant +++ b/Dockerfile.tenant @@ -30,6 +30,10 @@ RUN cd docs-site && yarn build FROM --platform=linux/amd64 nginx:1.27-alpine +ARG SERVICE_VERSION=0.0.0 +LABEL com.xuqm.version="${SERVICE_VERSION}" +LABEL com.xuqm.service="tenant-web" + COPY nginx/tenant.conf /etc/nginx/conf.d/default.conf COPY --from=build /workspace/tenant-platform/dist /usr/share/nginx/html/tenant COPY --from=build /workspace/docs-site/docs/.vitepress/dist /usr/share/nginx/html/docs diff --git a/Jenkinsfile b/Jenkinsfile index 7a70eb0..152a928 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,6 @@ pipeline { parameters { choice(name: 'APP', choices: ['tenant-platform', 'ops-platform'], description: '要构建的 Web 应用') - string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag') booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署') booleanParam(name: 'NO_CACHE', defaultValue: false, description: '禁用 Docker 构建缓存(缓存错误时使用)') } @@ -15,6 +14,7 @@ pipeline { PROD_HOST = '106.54.23.149' PROD_USER = 'ubuntu' COMPOSE_FILE = '/opt/xuqm/deploy/compose.production.yaml' + VERSIONS_FILE = '/opt/xuqm/deploy/versions.json' DOCKER_BUILDKIT = '1' } @@ -44,17 +44,28 @@ pipeline { env.IMAGE_NAME = 'tenant-web' env.DOCKERFILE = 'Dockerfile.tenant' env.DEPLOY_SERVICE = 'tenant-web' + env.VERSION_FILE = 'VERSION.tenant-web' env.BUILD_ARGS = '--build-arg TENANT_APP_BASE=/ --build-arg TENANT_API_BASE_URL=/api' break case 'ops-platform': env.IMAGE_NAME = 'ops-web' env.DOCKERFILE = 'Dockerfile.ops' env.DEPLOY_SERVICE = 'ops-web' + env.VERSION_FILE = 'VERSION.ops-web' env.BUILD_ARGS = '--build-arg OPS_APP_BASE=/ --build-arg OPS_API_BASE_URL=/api' break default: error("Unsupported APP: ${params.APP}") } + + // 自动递增版本号(与后端 Jenkinsfile 保持一致) + def vf = env.VERSION_FILE + def current = fileExists(vf) ? readFile(vf).trim() : '1.0.0' + def parts = current.tokenize('.') + while (parts.size() < 3) parts.add('0') + env.SERVICE_VERSION = "${parts[0]}.${parts[1]}.${parts[2].toInteger() + 1}" + writeFile file: vf, text: env.SERVICE_VERSION + echo "${env.IMAGE_NAME}: ${current} → ${env.SERVICE_VERSION}" } } } @@ -63,20 +74,23 @@ pipeline { steps { withCredentials([string(credentialsId: 'ACR_PASSWORD', variable: 'ACR_PASS')]) { script { - def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}" + def versionedImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${env.SERVICE_VERSION}" + def latestImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:latest" def cacheArgs = params.NO_CACHE ? '--no-cache' - : "--build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${fullImage}" + : "--build-arg BUILDKIT_INLINE_CACHE=1 --cache-from ${latestImage}" def gitCommit = env.GIT_COMMIT ?: 'unknown' bat """ docker login ${env.ACR_REGISTRY} -u ${env.ACR_USERNAME} -p %ACR_PASS% if %errorlevel% neq 0 exit /b 1 - docker pull --platform=linux/amd64 ${fullImage} || echo Pull failed, will build fresh - docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg GIT_COMMIT=${gitCommit} ${cacheArgs} -t ${fullImage} . + docker pull --platform=linux/amd64 ${latestImage} || echo Pull failed, will build fresh + docker build --platform=linux/amd64 -f ${env.DOCKERFILE} ${env.BUILD_ARGS} --build-arg GIT_COMMIT=${gitCommit} --build-arg SERVICE_VERSION=${env.SERVICE_VERSION} ${cacheArgs} -t ${versionedImage} -t ${latestImage} . if %errorlevel% neq 0 exit /b 1 - docker push ${fullImage} + docker push ${versionedImage} if %errorlevel% neq 0 exit /b 1 - docker rmi ${fullImage} + docker push ${latestImage} + if %errorlevel% neq 0 exit /b 1 + docker rmi ${versionedImage} ${latestImage} exit /b 0 """ } @@ -90,21 +104,55 @@ pipeline { lock('prod-deploy') { withCredentials([sshUserPrivateKey(credentialsId: 'PROD_SSH_KEY', keyFileVariable: 'SSH_KEY')]) { script { - def fullImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:${params.IMAGE_TAG}" + def latestImage = "${env.ACR_REGISTRY}/${env.ACR_NAMESPACE}/${env.IMAGE_NAME}:latest" retry(2) { bat """ - ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker image prune -f 2>/dev/null || true; docker pull ${fullImage} || exit 1; docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} || exit 1; docker image prune -f" + ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "docker image prune -f 2>/dev/null || true; docker pull ${latestImage} || exit 1; docker compose -f ${env.COMPOSE_FILE} up -d --no-deps --force-recreate ${env.DEPLOY_SERVICE} || exit 1; docker image prune -f" """ } + + // 更新 versions.json(与后端 Jenkinsfile 保持一致的合并逻辑) + def svcName = env.DEPLOY_SERVICE + def svcVer = env.SERVICE_VERSION + def releasedAt = new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone('UTC')) + def updateScript = """\ +import json, os +path = '${env.VERSIONS_FILE}' +d = json.load(open(path)) if os.path.exists(path) else {} +d.setdefault('services', {}) +d['services'].setdefault('${svcName}', {}) +d['services']['${svcName}']['version'] = '${svcVer}' +d['services']['${svcName}']['changed'] = True +d['releasedAt'] = '${releasedAt}' +json.dump(d, open(path, 'w'), indent=2, ensure_ascii=False) +print('versions.json updated: ${svcName}=${svcVer}') +""".stripIndent() + writeFile file: 'update_versions_web.py', text: updateScript + bat """ + scp -i "%SSH_KEY%" -o StrictHostKeyChecking=no update_versions_web.py ${env.PROD_USER}@${env.PROD_HOST}:/tmp/update_versions_web.py + ssh -i "%SSH_KEY%" -o StrictHostKeyChecking=no ${env.PROD_USER}@${env.PROD_HOST} "python3 /tmp/update_versions_web.py && rm /tmp/update_versions_web.py" + """ } } } } } + + stage('Commit Version') { + steps { + bat """ + git config user.email "jenkins@xuqm.com" + git config user.name "Jenkins CI" + git add ${env.VERSION_FILE} + git diff --cached --quiet || git commit -m "ci: bump ${env.IMAGE_NAME} to ${env.SERVICE_VERSION} [skip ci]" + git push origin HEAD:main + """ + } + } } post { - success { echo "✅ ${env.IMAGE_NAME}:${params.IMAGE_TAG} 构建部署成功" } + success { echo "✅ ${env.IMAGE_NAME}:${env.SERVICE_VERSION} 构建部署成功" } failure { echo "❌ 构建失败,请检查日志" } } } diff --git a/tenant-platform/src/views/security/SecurityCenterView.vue b/tenant-platform/src/views/security/SecurityCenterView.vue index 60bb251..abbca26 100644 --- a/tenant-platform/src/views/security/SecurityCenterView.vue +++ b/tenant-platform/src/views/security/SecurityCenterView.vue @@ -55,34 +55,49 @@ - - - {{ currentVersion }} - 加载中... - 检查更新 - - - {{ updateCheckResult.latestVersion }} - - {{ formatTime(updateCheckResult.releasedAt) }} - - - -
{{ updateCheckResult.changelog }}
-
- -
- - {{ svc }} {{ info.current }}{{ info.changed ? ' → ' + info.latest : '' }} +
+ + 检查更新 + + 所有服务已是最新 + + 有 {{ changedServiceCount }} 个服务可更新 + + + 云端发布于 {{ formatTime(updateCheckResult.releasedAt) }} + +
+ + + + + + + + + + + + + + +
{{ updateCheckResult?.hasUpdate ? '一键更新到最新版本' : '一键更新' }} @@ -250,7 +265,7 @@