fix(cicd): 修复 web 流水线 - 路径/构建脚本/飞书凭证/部署方式
- Dockerfile: apps/web-antd → apps/web-antdv-next(实际落点) - Dockerfile: pnpm build:antd(不存在)→ turbo build --filter=@vben/web-antdv-next - Dockerfile: 新增 nginx.conf(原 COPY 目标文件不存在导致 build 失败) - Jenkinsfile: 移除飞书通知(凭证 feishu-* 未配,导致构建启动即 FAIL) - Jenkinsfile: 部署 node(deploy-agent)→ SSH 直连 cd-target(无该 agent 节点) - Jenkinsfile: 清除硬编码 webhook URL 明文
This commit is contained in:
parent
29f3b3b451
commit
5696168bc3
26
Dockerfile
26
Dockerfile
@ -1,30 +1,32 @@
|
||||
# BuildFlow Web (Vben Admin - web-antd) 多阶段构建
|
||||
# BuildFlow Web (web-antdv-next / @vben/web-antdv-next) 多阶段构建
|
||||
# 第一阶段:Node 22 构建 SPA 产物;第二阶段:Nginx 托管静态资源
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# 全局装 pnpm (与项目对齐)
|
||||
RUN npm install -g pnpm@9
|
||||
# 全局装 pnpm(与本地 11.x 对齐;corepack 在 alpine 上不可靠)
|
||||
RUN npm install -g pnpm@11
|
||||
|
||||
# 先拷依赖清单利用层缓存
|
||||
# 先拷依赖清单利用层缓存(pnpm workspace + turbo 需要这些清单文件齐备)
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
|
||||
COPY apps/web-antd/package.json ./apps/web-antd/package.json
|
||||
|
||||
# 拷贝 internal/scripts (turbo/vsh 工具链需要)
|
||||
COPY internal/ ./internal/
|
||||
COPY scripts/ ./scripts/
|
||||
COPY apps/web-antdv-next/package.json ./apps/web-antdv-next/package.json
|
||||
|
||||
# 拷贝其余 packages 的 package.json(workspace 依赖解析需要),但不拷 node_modules
|
||||
COPY packages/ ./packages/
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# 拷贝全部源码
|
||||
COPY . .
|
||||
|
||||
# 构建 web-antd 变体 (质量门禁: lint + typecheck 内含于 build)
|
||||
RUN pnpm build:antd
|
||||
# 构建 web-antdv-next 变体(turbo 按 workspace 名过滤;质量门禁内含 typecheck)
|
||||
RUN pnpm run build --filter=@vben/web-antdv-next...
|
||||
|
||||
# ---- 运行阶段: nginx 托管静态资源 ----
|
||||
# ---- 运行阶段:Nginx 托管静态资源 ----
|
||||
FROM nginx:1.27-alpine AS runtime
|
||||
COPY apps/web-antd/nginx.conf /etc/nginx/conf.d/default.conf 2>/dev/null || true
|
||||
COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html
|
||||
COPY apps/web-antdv-next/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/apps/web-antdv-next/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -q --spider http://127.0.0.1/ || exit 1
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
84
Jenkinsfile
vendored
84
Jenkinsfile
vendored
@ -1,42 +1,6 @@
|
||||
// BuildFlow buildflow-web CI/CD - clean version
|
||||
// ①拉取代码 -> ②编译打包 -> 推送Harbor -> ③部署 + 飞书通知
|
||||
|
||||
def feishuNotify = { String result, String failedStageName = "" ->
|
||||
def dur = currentBuild.durationString.replace(" and counting", "")
|
||||
def commit = ""
|
||||
try { commit = sh(script: "git log -1 --pretty=format:%s", returnStdout: true).trim() } catch(ignored) {}
|
||||
def isOk = (result == "SUCCESS")
|
||||
def color = isOk ? "green" : "red"
|
||||
def statusText = isOk ? "成功" : "失败"
|
||||
def title = "CI/CD " + statusText + " " + env.JOB_NAME + " #" + env.BUILD_NUMBER
|
||||
def fields = [
|
||||
[is_short: true, text: [tag: "lark_md", content: "**项目**\n" + env.JOB_NAME]],
|
||||
[is_short: true, text: [tag: "lark_md", content: "**耗时**\n" + dur]],
|
||||
[is_short: true, text: [tag: "lark_md", content: "**提交**\n" + commit.take(50)]],
|
||||
[is_short: true, text: [tag: "lark_md", content: "**状态**\n" + statusText]]
|
||||
]
|
||||
def elements = [[tag: "div", fields: fields], [tag: "hr"]]
|
||||
if (isOk) {
|
||||
elements.add([tag: "div", text: [tag: "lark_md", content: "镜像地址\n`" + env.IMAGE + ":" + env.TAG + "`"]])
|
||||
} else if (failedStageName) {
|
||||
elements.add([tag: "div", text: [tag: "lark_md", content: "失败阶段\n" + failedStageName]])
|
||||
}
|
||||
def burl = env.BUILD_URL
|
||||
if (burl == null) { burl = "http://115.191.51.246:8080/job/" + env.JOB_NAME.replace("/", "/job/") + "/" }
|
||||
elements.add([tag: "action", actions: [[tag: "button", text: [tag: "plain_text", content: "查看构建"], url: burl, type: isOk ? "primary" : "danger"]]])
|
||||
def card = [msg_type: "interactive", card: [header: [title: [tag: "plain_text", content: title], template: color], elements: elements]]
|
||||
def ts = String.valueOf(System.currentTimeMillis() / 1000L as long)
|
||||
def stringToSign = ts + "\n" + "Qp6SwLIkgRJEtV9UsOf6mg"
|
||||
def mac = javax.crypto.Mac.getInstance("HmacSHA256")
|
||||
mac.init(new javax.crypto.spec.SecretKeySpec(stringToSign.getBytes("UTF-8"), "HmacSHA256"))
|
||||
def sign = mac.doFinal().encodeBase64().toString()
|
||||
card.timestamp = ts
|
||||
card.sign = sign
|
||||
def jsonStr = groovy.json.JsonOutput.toJson(card)
|
||||
writeFile file: "/tmp/feishu_msg.json", text: jsonStr
|
||||
sh 'curl -sS -X POST https://open.feishu.cn/open-apis/bot/v2/hook/921d9874-de0a-4eca-9ca6-66bf91bd2fbc -H "Content-Type: application/json" -d @/tmp/feishu_msg.json || true'
|
||||
sh 'rm -f /tmp/feishu_msg.json'
|
||||
}
|
||||
// BuildFlow buildflow-web CI/CD
|
||||
// 链路:①拉取代码 → ②编译打包(docker build) → 推送 Harbor → ③部署(cd-target SSH) → HealthCheck
|
||||
// 注:飞书通知逻辑已移除(凭证未配齐);部署通过 ssh 直连 cd-target 执行。
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
@ -44,6 +8,9 @@ pipeline {
|
||||
HARBOR = "43.134.235.58:58080"
|
||||
IMAGE = "${HARBOR}/buildflow/buildflow-web"
|
||||
TAG = "${env.BUILD_NUMBER}"
|
||||
DEPLOY_HOST = "cd-target"
|
||||
APP_PORT = "80" // 容器内 nginx 监听 80;宿主映射 5175:80
|
||||
HOST_PORT = "5175"
|
||||
}
|
||||
options {
|
||||
timeout(time: 30, unit: "MINUTES")
|
||||
@ -78,29 +45,40 @@ pipeline {
|
||||
steps {
|
||||
script {
|
||||
try {
|
||||
node("deploy-agent") {
|
||||
sh '''
|
||||
docker pull $IMAGE:latest
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} '
|
||||
docker pull ${IMAGE}:latest
|
||||
docker rm -f buildflow-web 2>/dev/null || true
|
||||
docker run -d --name buildflow-web --restart unless-stopped -p 5175:80 $IMAGE:latest
|
||||
'''
|
||||
}
|
||||
docker run -d --name buildflow-web --restart unless-stopped -p ${HOST_PORT}:${APP_PORT} ${IMAGE}:latest
|
||||
'
|
||||
"""
|
||||
echo "部署完成"
|
||||
} catch (e) {
|
||||
echo "部署节点暂不可用,跳过自动部署。镜像已推送 Harbor。"
|
||||
echo "警告:部署节点 ${DEPLOY_HOST} 暂不可用,跳过自动部署(镜像已推送 Harbor)。原因:${e.getMessage()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("HealthCheck") {
|
||||
steps {
|
||||
script {
|
||||
try {
|
||||
sh """
|
||||
sleep 5
|
||||
ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} 'wget -q --spider http://127.0.0.1:${HOST_PORT}/ && echo HEALTHCHECK_OK'
|
||||
"""
|
||||
echo "=== HealthCheck OK: web http://110.42.240.10:${HOST_PORT} ==="
|
||||
} catch (e) {
|
||||
echo "警告:HealthCheck 探测失败(容器可能仍在启动)。镜像已推送 Harbor,可手动检查。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
script { feishuNotify("SUCCESS") }
|
||||
}
|
||||
failure {
|
||||
script { feishuNotify("FAILURE", "构建失败,请查看日志") }
|
||||
}
|
||||
always {
|
||||
success { echo '=== buildflow-web CI/CD SUCCESS ===' }
|
||||
failure { echo '=== buildflow-web CI/CD FAILED ===' }
|
||||
always {
|
||||
sh "docker rmi ${IMAGE}:${TAG} ${IMAGE}:latest 2>/dev/null || true"
|
||||
deleteDir()
|
||||
}
|
||||
|
||||
38
apps/web-antdv-next/nginx.conf
Normal file
38
apps/web-antdv-next/nginx.conf
Normal file
@ -0,0 +1,38 @@
|
||||
# BuildFlow Web (web-antdv-next) Nginx 运行配置
|
||||
# 仅 server 块,作为 conf.d/default.conf 覆盖 nginx 默认站点
|
||||
# - SPA history 路由回退 index.html
|
||||
# - /api 反代到后端(生产由部署侧决定是否启用)
|
||||
# - 静态资源长缓存
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml text/javascript
|
||||
application/javascript application/json application/xml
|
||||
application/rss+xml image/svg+xml font/woff font/woff2;
|
||||
|
||||
# SPA history fallback
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Vite 产物带 hash 文件名,可安全 immutable 长缓存
|
||||
location /assets/ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# 安全响应头
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user