Files
2026-09-14 01:54:26 +08:00

129 lines
4.8 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: 构建并部署前端
on:
push:
tags: ['v*']
workflow_dispatch:
concurrency:
group: deploy-frontend
cancel-in-progress: false
env:
# 生产机 nginx root 目录(静态资源部署目标)
DEPLOY_DIR: /www/zt-closet-manage.lzcwy.cn
# 蓝绿切换用的临时目录(位于 nginx root 之外,不会被对外访问)
STAGING_DIR: /www/.zt-closet-deploy-tmp
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 拉取代码
uses: https://gitee.com/liu_zhengchi/checkout@v4
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
# 匹配 package.json engines: ^22.18.0 || >=24.12.0
node-version: '22'
cache: 'npm'
- name: 安装依赖
run: npm ci
- name: 类型检查并构建
run: npm run build
- name: 确认构建产物
run: |
if [ ! -f dist/index.html ]; then
echo "构建产物 dist/index.html 不存在,构建可能失败" >&2
exit 1
fi
echo "构建产物:"
ls -la dist
- name: SSH 部署到生产机(蓝绿原子切换)
id: deploy
env:
SSH_KEY: ${{ secrets.ZT_CLOSET_PROD_SSH_KEY }}
SSH_HOST: ${{ vars.ZT_CLOSET_PROD_SSH_HOST }}
SSH_USER: ${{ vars.ZT_CLOSET_PROD_SSH_USER }}
run: |
# runner 默认未安装 rsync,先装上(生产机一般自带,无需处理)
if ! command -v rsync >/dev/null 2>&1; then
sudo apt-get update -y && sudo apt-get install -y rsync
fi
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
SSHOPT="-i ~/.ssh/deploy_key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
# 1) 先把产物 rsync 到远端临时目录(--delete 清理上一版遗留的旧 hash 资源),
# 此步骤绝不触碰线上目录,rsync 中断也不会影响线上。
rsync -az --delete -e "ssh $SSHOPT" dist/ "$SSH_USER@$SSH_HOST:$STAGING_DIR/"
# 2) 远端校验临时目录 + 蓝绿原子切换(mv)
ssh $SSHOPT "$SSH_USER@$SSH_HOST" 'bash -s' <<'REMOTE'
set -e
DIR="/www/zt-closet-manage.lzcwy.cn"
TMP="/www/.zt-closet-deploy-tmp"
BACKUP="/www/.zt-closet-backup-$(date +%Y%m%d%H%M%S)"
# 校验临时目录产物完整(否则中止,线上版本不受任何影响)
if [ ! -f "$TMP/index.html" ]; then
echo "临时目录缺少 index.html,部署中止,线上版本保持不变" >&2
exit 1
fi
# 首次部署时线上目录可能不存在,做兼容
if [ -d "$DIR" ]; then
mv "$DIR" "$BACKUP"
fi
mv "$TMP" "$DIR"
# 切换后校验,失败则秒级回滚(把备份 mv 回去)
if [ ! -f "$DIR/index.html" ]; then
echo "切换后校验失败,执行回滚" >&2
rm -rf "$DIR"
if [ -d "$BACKUP" ]; then
mv "$BACKUP" "$DIR"
fi
exit 1
fi
# 成功:仅保留最近一次备份,清理更早的
ls -1dt /www/.zt-closet-backup-* 2>/dev/null | tail -n +2 | xargs -r rm -rf
echo "部署成功:$DIR"
REMOTE
- name: 部署结果
if: always()
run: echo "前端部署流程结束(目标 ${{ env.DEPLOY_DIR }})"
- name: 通知钉钉
if: always()
env:
# 值为完整 Webhook 地址(https://oapi.dingtalk.com/robot/send?access_token=xxx)
DINGTALK_WEBHOOK: ${{ secrets.ZT_CLOSET_DINGTALK_WEBHOOK }}
# 部署步骤的结论:success / failure / skipped / cancelled(部署被跳过或失败均视为失败)
DEPLOY_OUTCOME: ${{ steps.deploy.outcome }}
run: |
if [ "$DEPLOY_OUTCOME" = "success" ]; then
STATUS="✅ 前端部署成功"
else
STATUS="❌ 前端部署失败"
fi
# 单行拼装 payload:避免多行双引号转义导致 shell 语法错误;\n 交由 JSON 解析为换行
TEXT="## 前端部署通知\n\n**结果**:${STATUS}\n\n**触发人**:${{ gitea.actor }}\n**分支/标签**:${{ gitea.ref_name }}\n**提交**:[${{ gitea.sha }}](${{ gitea.server_url }}/${{ gitea.repository }}/commit/${{ gitea.sha }})\n**流水线**:[${{ gitea.run_id }}](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }})"
PAYLOAD="{\"msgtype\":\"markdown\",\"markdown\":{\"title\":\"前端部署通知\",\"text\":\"${TEXT}\"}}"
curl -s -X POST "${DINGTALK_WEBHOOK}" \
-H 'Content-Type: application/json' \
-d "$PAYLOAD"
echo "钉钉通知已发送:${STATUS}"