|
13995
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
export API_IMAGE="${API_IMAGE}"
export FRONTEND_IMAGE="${FRONTEND_IMAGE}"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (\${RETRY}/\${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "${VERSION}" > .deployed_version
echo "==> Staging 部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
13996
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到 Production
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-production:
name: 部署到 Production
runs-on: ubuntu-latest
if: >-
startsWith(github.ref, 'refs/tags/v') || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_PATH: /opt/juhi
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql"
docker compose -f docker-compose.prod.yml exec -T postgres \
pg_dump -U "${DB_USER:-juhi}" -d "${DB_NAME:-juhi_revops}" -Fc > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "pre_deploy_*.sql" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- name: 部署到 Production
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
export API_IMAGE="${API_IMAGE}"
export FRONTEND_IMAGE="${FRONTEND_IMAGE}"
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 记录部署历史
CURRENT_API=\$(docker inspect --format='{{.Config.Image}}' juhi-api 2>/dev/null || echo "none")
CURRENT_FE=\$(docker inspect --format='{{.Config.Image}}' juhi-frontend 2>/dev/null || echo "none")
echo "\$(date -Iseconds)|\${CURRENT_API}|\${CURRENT_FE}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
# 蓝绿部署
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=2 api
RETRY=0
MAX_RETRY=15
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "健康检查超时,回滚到单实例"
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=1 api
exit 1
fi
sleep 5
done
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=1 api
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "${VERSION}" > .deployed_version
echo "==> Production 部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Production 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick || exit 1
else
curl -sf http://localhost:3000/health || exit 1
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "Production 验证通过"
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-production
|
["gate","build-and-push","depl ["gate","build-and-push","deploy-staging"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
13997
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到阿里云
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
docker compose -f docker-compose.prod.yml pull api frontend
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
sleep 5
done
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
13998
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
Production 自动回滚
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
auto-rollback-production:
name: Production 自动回滚
runs-on: ubuntu-latest
if: failure() && needs.deploy-production.result == 'failure'
steps:
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 执行回滚
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> Production 自动回滚..."
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --confirm -y
else
PREV_LINE=$(tail -1 .deploy-history 2>/dev/null)
PREV_API=$(echo "$PREV_LINE" | cut -d'|' -f2)
PREV_FE=$(echo "$PREV_LINE" | cut -d'|' -f3)
if [ -n "$PREV_API" ] && [ "$PREV_API" != "none" ]; then
export API_IMAGE="$PREV_API"
export FRONTEND_IMAGE="$PREV_FE"
docker compose -f docker-compose.prod.yml up -d --no-deps api frontend
RETRY=0
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
[ $RETRY -ge 12 ] && { echo "回滚后健康检查失败"; exit 1; }
sleep 5
done
echo "==> 回滚成功"
else
echo "无法获取上一版本,需要手动回滚"
exit 1
fi
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
timeout-minutes: "10"
...
|
auto-rollback-production
|
["deploy-production"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
13999
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
阿里云自动回滚
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
auto-rollback-aliyun:
name: 阿里云自动回滚
runs-on: ubuntu-latest
if: failure() && needs.deploy-aliyun.result == 'failure'
steps:
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 执行回滚
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> 阿里云自动回滚..."
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --version 1 --confirm
else
PREV_VERSION=$(tail -1 .deploy-history 2>/dev/null | cut -d'|' -f3)
if [ -n "$PREV_VERSION" ]; then
docker compose -f docker-compose.prod.yml up -d --no-deps api frontend
echo "==> 回滚完成"
else
echo "无法获取上一版本"
exit 1
fi
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
timeout-minutes: "10"
...
|
auto-rollback-aliyun
|
["deploy-aliyun"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
14000
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
手动回滚
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
manual-rollback:
name: 手动回滚
runs-on: ubuntu-latest
if: needs.gate.outputs.is_rollback == 'true'
steps:
- uses: actions/checkout@v4
- if: needs.gate.outputs.target_env == 'rollback-production'
name: 配置 SSH(Production)
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- if: needs.gate.outputs.target_env == 'rollback-aliyun'
name: 配置 SSH(阿里云)
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
if [ "${{ needs.gate.outputs.target_env }}" == "rollback-production" ]; then
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
else
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
fi
chmod 644 ~/.ssh/known_hosts
- name: 执行回滚
run: |
if [ "${{ needs.gate.outputs.target_env }}" == "rollback-production" ]; then
HOST="${{ secrets.PRODUCTION_HOST }}"
USER="${{ secrets.PRODUCTION_USER }}"
else
HOST="${{ secrets.ALIYUN_HOST }}"
USER="${{ secrets.ALIYUN_USER }}"
fi
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> 手动回滚..."
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --version 1 --confirm
else
echo "rollback.sh 不存在,需要手动操作"
echo "部署历史:"
tail -10 .deploy-history 2>/dev/null || echo "(无记录)"
exit 1
fi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
fi
EOF
timeout-minutes: "10"
...
|
manual-rollback
|
["gate"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
14001
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署通知
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
notify:
name: 部署通知
runs-on: ubuntu-latest
if: always()
steps:
- name: 生成部署报告
run: |
echo "## CI/CD 部署报告" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **触发**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **分支**: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **提交**: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **触发者**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| 阶段 | 状态 |" >> $GITHUB_STEP_SUMMARY
echo "|------|------|" >> $GITHUB_STEP_SUMMARY
echo "| Staging | ${{ needs.deploy-staging.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Production | ${{ needs.deploy-production.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| 阿里云 | ${{ needs.deploy-aliyun.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| 手动回滚 | ${{ needs.manual-rollback.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
- if: always() && (secrets.SLACK_WEBHOOK != '')
name: Slack 通知
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: CI/CD Deploy - ${{ needs.gate.outputs.target_env }} - ${{ github.ref_name }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
notify
|
["gate","deploy-staging","depl ["gate","deploy-staging","deploy-production","deploy-aliyun","manual-rollback","auto-rollback-production","auto-rollback-aliyun"]...
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880521
|
1774879496
|
1774880521
|
|
0
|
|
0
|
Edit
Delete
|
|
14002
|
9764
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11502
|
4
|
1774880521
|
1774880521
|
1774879518
|
1774880522
|
|
0
|
|
0
|
Edit
Delete
|
|
14003
|
9765
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11503
|
4
|
1774880523
|
1774880523
|
1774879818
|
1774880524
|
|
0
|
|
0
|
Edit
Delete
|
|
14004
|
9766
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11504
|
4
|
1774880525
|
1774880525
|
1774880118
|
1774880526
|
|
0
|
|
0
|
Edit
Delete
|
|
14005
|
9767
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11505
|
4
|
1774880527
|
1774880527
|
1774880418
|
1774880528
|
|
0
|
|
0
|
Edit
Delete
|
|
14006
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署门禁
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
gate:
name: 部署门禁
runs-on: ubuntu-latest
steps:
- id: check
name: 检查部署条件
run: |
SHOULD_DEPLOY="false"
IS_ROLLBACK="false"
TARGET_ENV="staging"
# 回滚请求
if [[ "${{ github.event.inputs.environment }}" == rollback-* ]]; then
IS_ROLLBACK="true"
TARGET_ENV="${{ github.event.inputs.environment }}"
SHOULD_DEPLOY="true"
# 手动触发
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TARGET_ENV="${{ github.event.inputs.environment }}"
SHOULD_DEPLOY="true"
# 版本标签
elif [ "${{ github.event_name }}" == "push" ]; then
TARGET_ENV="production"
SHOULD_DEPLOY="true"
# test-pipeline 通过后自动部署 staging
elif [ "${{ github.event_name }}" == "workflow_run" ]; then
if [ "${{ github.event.workflow_run.conclusion }}" == "success" ]; then
TARGET_ENV="staging"
SHOULD_DEPLOY="true"
else
echo "Test Pipeline 未通过,跳过部署"
fi
fi
echo "should_deploy=$SHOULD_DEPLOY" >> $GITHUB_OUTPUT
echo "is_rollback=$IS_ROLLBACK" >> $GITHUB_OUTPUT
echo "target_env=$TARGET_ENV" >> $GITHUB_OUTPUT
echo "## 部署门禁" >> $GITHUB_STEP_SUMMARY
echo "- 触发方式: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "- 目标环境: $TARGET_ENV" >> $GITHUB_STEP_SUMMARY
echo "- 允许部署: $SHOULD_DEPLOY" >> $GITHUB_STEP_SUMMARY
echo "- 回滚模式: $IS_ROLLBACK" >> $GITHUB_STEP_SUMMARY
outputs:
is_rollback: ${{ steps.check.outputs.is_rollback }}
should_deploy: ${{ steps.check.outputs.should_deploy }}
target_env: ${{ steps.check.outputs.target_env }}
...
|
gate
|
null
|
["ubuntu-latest"]
|
11506
|
1
|
1774880529
|
1774880529
|
1774880521
|
1774880530
|
|
0
|
|
0
|
Edit
Delete
|
|
14007
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
紧急验证(跳过测试时)
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
quick-check:
name: 紧急验证(跳过测试时)
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.should_deploy == 'true' && needs.gate.outputs.is_rollback == 'false' && github.event.inputs.skip_tests == 'true'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v4
with:
cache: pnpm
node-version: ${{ env.NODE_VERSION }}
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 构建共享包
run: pnpm --filter @juhi/shared run build
- name: 后端类型检查
run: pnpm --filter juhi-api run type-check
- name: 前端类型检查
run: pnpm --filter juhi-frontend run type-check
timeout-minutes: "10"
...
|
quick-check
|
["gate"]
|
["ubuntu-latest"]
|
11507
|
4
|
1774880531
|
1774880532
|
1774880521
|
1774880532
|
|
1
|
|
0
|
Edit
Delete
|
|
14008
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
构建并推送镜像
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
build-and-push:
name: 构建并推送镜像
runs-on: ubuntu-latest
if: >-
always() && needs.gate.outputs.should_deploy == 'true' && needs.gate.outputs.is_rollback == 'false' && (needs.quick-check.result == 'success' || needs.quick-check.result == 'skipped')
steps:
- uses: actions/checkout@v4
- id: version
name: 获取版本号
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
elif [ -n "${{ github.event.inputs.version }}" ]; then
VERSION=${{ github.event.inputs.version }}
else
VERSION=$(date +%Y%m%d)-${{ github.run_number }}
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录容器仓库
uses: docker/login-action@v3
with:
password: ${{ secrets.GITHUB_TOKEN }}
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
- id: meta-api
name: 后端镜像元数据
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api
tags: |
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: 构建并推送后端镜像
uses: docker/build-push-action@v5
with:
build-args: NODE_ENV=production
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: backend/Dockerfile
labels: ${{ steps.meta-api.outputs.labels }}
push: "true"
tags: ${{ steps.meta-api.outputs.tags }}
- id: meta-frontend
name: 前端镜像元数据
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-frontend
tags: |
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=latest
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: 构建并推送前端镜像
uses: docker/build-push-action@v5
with:
build-args: VITE_API_BASE_URL=/api/v1
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: frontend/Dockerfile
labels: ${{ steps.meta-frontend.outputs.labels }}
push: "true"
tags: ${{ steps.meta-frontend.outputs.tags }}
- name: 输出构建信息
run: |
echo "## 构建信息" >> $GITHUB_STEP_SUMMARY
echo "- 版本: \`${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "- 提交: \`${{ steps.version.outputs.short_sha }}\`" >> $GITHUB_STEP_SUMMARY
timeout-minutes: "20"
outputs:
api-image: ${{ steps.meta-api.outputs.tags }}
frontend-image: ${{ steps.meta-frontend.outputs.tags }}
version: ${{ steps.version.outputs.version }}
permissions:
contents: read
packages: write
...
|
build-and-push
|
["gate","quick-check"]
|
["ubuntu-latest"]
|
11509
|
4
|
1774880535
|
1774880535
|
1774880521
|
1774880536
|
|
1
|
|
0
|
Edit
Delete
|
|
14009
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
export API_IMAGE="${API_IMAGE}"
export FRONTEND_IMAGE="${FRONTEND_IMAGE}"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (\${RETRY}/\${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "${VERSION}" > .deployed_version
echo "==> Staging 部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11510
|
4
|
1774880539
|
1774880539
|
1774880521
|
1774880540
|
|
1
|
|
0
|
Edit
Delete
|
|
14010
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到 Production
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-production:
name: 部署到 Production
runs-on: ubuntu-latest
if: >-
startsWith(github.ref, 'refs/tags/v') || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_PATH: /opt/juhi
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql"
docker compose -f docker-compose.prod.yml exec -T postgres \
pg_dump -U "${DB_USER:-juhi}" -d "${DB_NAME:-juhi_revops}" -Fc > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "pre_deploy_*.sql" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- name: 部署到 Production
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
export API_IMAGE="${API_IMAGE}"
export FRONTEND_IMAGE="${FRONTEND_IMAGE}"
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 记录部署历史
CURRENT_API=\$(docker inspect --format='{{.Config.Image}}' juhi-api 2>/dev/null || echo "none")
CURRENT_FE=\$(docker inspect --format='{{.Config.Image}}' juhi-frontend 2>/dev/null || echo "none")
echo "\$(date -Iseconds)|\${CURRENT_API}|\${CURRENT_FE}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
# 蓝绿部署
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=2 api
RETRY=0
MAX_RETRY=15
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "健康检查超时,回滚到单实例"
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=1 api
exit 1
fi
sleep 5
done
docker compose -f docker-compose.prod.yml up -d --no-deps --scale api=1 api
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "${VERSION}" > .deployed_version
echo "==> Production 部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Production 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick || exit 1
else
curl -sf http://localhost:3000/health || exit 1
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "Production 验证通过"
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-production
|
["gate","build-and-push","depl ["gate","build-and-push","deploy-staging"]...
|
["ubuntu-latest"]
|
11512
|
4
|
1774880543
|
1774880543
|
1774880521
|
1774880544
|
|
1
|
|
0
|
Edit
Delete
|
|
14011
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到阿里云
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
deploy-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
docker compose -f docker-compose.prod.yml pull api frontend
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ \$? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=\$((RETRY + 1))
if [ \$RETRY -ge \$MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
sleep 5
done
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11511
|
4
|
1774880541
|
1774880541
|
1774880521
|
1774880542
|
|
1
|
|
0
|
Edit
Delete
|
|
14012
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
Production 自动回滚
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
auto-rollback-production:
name: Production 自动回滚
runs-on: ubuntu-latest
if: failure() && needs.deploy-production.result == 'failure'
steps:
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 执行回滚
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> Production 自动回滚..."
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --confirm -y
else
PREV_LINE=$(tail -1 .deploy-history 2>/dev/null)
PREV_API=$(echo "$PREV_LINE" | cut -d'|' -f2)
PREV_FE=$(echo "$PREV_LINE" | cut -d'|' -f3)
if [ -n "$PREV_API" ] && [ "$PREV_API" != "none" ]; then
export API_IMAGE="$PREV_API"
export FRONTEND_IMAGE="$PREV_FE"
docker compose -f docker-compose.prod.yml up -d --no-deps api frontend
RETRY=0
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
[ $RETRY -ge 12 ] && { echo "回滚后健康检查失败"; exit 1; }
sleep 5
done
echo "==> 回滚成功"
else
echo "无法获取上一版本,需要手动回滚"
exit 1
fi
fi
EOF
env:
HOST: ${{ secrets.PRODUCTION_HOST }}
USER: ${{ secrets.PRODUCTION_USER }}
timeout-minutes: "10"
...
|
auto-rollback-production
|
["deploy-production"]
|
["ubuntu-latest"]
|
11514
|
4
|
1774880547
|
1774880547
|
1774880521
|
1774880548
|
|
1
|
|
0
|
Edit
Delete
|
|
14013
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
阿里云自动回滚
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
auto-rollback-aliyun:
name: 阿里云自动回滚
runs-on: ubuntu-latest
if: failure() && needs.deploy-aliyun.result == 'failure'
steps:
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 执行回滚
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> 阿里云自动回滚..."
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --version 1 --confirm
else
PREV_VERSION=$(tail -1 .deploy-history 2>/dev/null | cut -d'|' -f3)
if [ -n "$PREV_VERSION" ]; then
docker compose -f docker-compose.prod.yml up -d --no-deps api frontend
echo "==> 回滚完成"
else
echo "无法获取上一版本"
exit 1
fi
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
timeout-minutes: "10"
...
|
auto-rollback-aliyun
|
["deploy-aliyun"]
|
["ubuntu-latest"]
|
11513
|
4
|
1774880545
|
1774880546
|
1774880521
|
1774880546
|
|
1
|
|
0
|
Edit
Delete
|
|
14014
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
手动回滚
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
manual-rollback:
name: 手动回滚
runs-on: ubuntu-latest
if: needs.gate.outputs.is_rollback == 'true'
steps:
- uses: actions/checkout@v4
- if: needs.gate.outputs.target_env == 'rollback-production'
name: 配置 SSH(Production)
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.PRODUCTION_SSH_KEY }}
- if: needs.gate.outputs.target_env == 'rollback-aliyun'
name: 配置 SSH(阿里云)
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
if [ "${{ needs.gate.outputs.target_env }}" == "rollback-production" ]; then
echo "${{ secrets.PRODUCTION_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
else
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
fi
chmod 644 ~/.ssh/known_hosts
- name: 执行回滚
run: |
if [ "${{ needs.gate.outputs.target_env }}" == "rollback-production" ]; then
HOST="${{ secrets.PRODUCTION_HOST }}"
USER="${{ secrets.PRODUCTION_USER }}"
else
HOST="${{ secrets.ALIYUN_HOST }}"
USER="${{ secrets.ALIYUN_USER }}"
fi
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
echo "==> 手动回滚..."
if [ -f "./scripts/rollback.sh" ]; then
chmod +x ./scripts/rollback.sh
./scripts/rollback.sh --version 1 --confirm
else
echo "rollback.sh 不存在,需要手动操作"
echo "部署历史:"
tail -10 .deploy-history 2>/dev/null || echo "(无记录)"
exit 1
fi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
fi
EOF
timeout-minutes: "10"
...
|
manual-rollback
|
["gate"]
|
["ubuntu-latest"]
|
11508
|
4
|
1774880533
|
1774880533
|
1774880521
|
1774880534
|
|
1
|
|
0
|
Edit
Delete
|
|
14015
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署通知
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "8"
REGISTRY: ghcr.io
jobs:
notify:
name: 部署通知
runs-on: ubuntu-latest
if: always()
steps:
- name: 生成部署报告
run: |
echo "## CI/CD 部署报告" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **触发**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **分支**: \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **提交**: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **触发者**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| 阶段 | 状态 |" >> $GITHUB_STEP_SUMMARY
echo "|------|------|" >> $GITHUB_STEP_SUMMARY
echo "| Staging | ${{ needs.deploy-staging.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Production | ${{ needs.deploy-production.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| 阿里云 | ${{ needs.deploy-aliyun.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| 手动回滚 | ${{ needs.manual-rollback.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
- if: always() && (secrets.SLACK_WEBHOOK != '')
name: Slack 通知
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: CI/CD Deploy - ${{ needs.gate.outputs.target_env }} - ${{ github.ref_name }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
notify
|
["gate","deploy-staging","depl ["gate","deploy-staging","deploy-production","deploy-aliyun","manual-rollback","auto-rollback-production","auto-rollback-aliyun"]...
|
["ubuntu-latest"]
|
11515
|
1
|
1774880549
|
1774880582
|
1774880521
|
1774880583
|
|
1
|
|
0
|
Edit
Delete
|
|
14016
|
9769
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11516
|
4
|
1774880719
|
1774880719
|
1774880718
|
1774880719
|
|
0
|
|
0
|
Edit
Delete
|
|
14017
|
9770
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11517
|
4
|
1774881019
|
1774881019
|
1774881018
|
1774881019
|
|
0
|
|
0
|
Edit
Delete
|
|
14018
|
9771
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11518
|
4
|
1774881319
|
1774881319
|
1774881318
|
1774881319
|
|
0
|
|
0
|
Edit
Delete
|
|
14019
|
9772
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11519
|
4
|
1774881619
|
1774881619
|
1774881618
|
1774881619
|
|
0
|
|
0
|
Edit
Delete
|
|
14020
|
9773
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11520
|
4
|
1774881919
|
1774881919
|
1774881918
|
1774881919
|
|
0
|
|
0
|
Edit
Delete
|
|
14021
|
9774
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11521
|
4
|
1774882219
|
1774882219
|
1774882218
|
1774882219
|
|
0
|
|
0
|
Edit
Delete
|
|
14022
|
9775
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11522
|
4
|
1774882519
|
1774882519
|
1774882518
|
1774882519
|
|
0
|
|
0
|
Edit
Delete
|
|
14023
|
9776
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11523
|
4
|
1774882819
|
1774882819
|
1774882818
|
1774882819
|
|
0
|
|
0
|
Edit
Delete
|
|
14024
|
9777
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11524
|
4
|
1774883119
|
1774883119
|
1774883118
|
1774883119
|
|
0
|
|
0
|
Edit
Delete
|
|
14025
|
9778
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11525
|
4
|
1774883419
|
1774883419
|
1774883418
|
1774883419
|
|
0
|
|
0
|
Edit
Delete
|
|
14026
|
9779
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11526
|
4
|
1774883719
|
1774883719
|
1774883718
|
1774883719
|
|
0
|
|
0
|
Edit
Delete
|
|
14027
|
9780
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11527
|
4
|
1774884019
|
1774884019
|
1774884018
|
1774884019
|
|
0
|
|
0
|
Edit
Delete
|
|
14028
|
9781
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11528
|
4
|
1774884319
|
1774884319
|
1774884318
|
1774884319
|
|
0
|
|
0
|
Edit
Delete
|
|
14029
|
9782
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11529
|
4
|
1774884619
|
1774884619
|
1774884618
|
1774884619
|
|
0
|
|
0
|
Edit
Delete
|
|
14030
|
9783
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11530
|
4
|
1774884919
|
1774884919
|
1774884918
|
1774884919
|
|
0
|
|
0
|
Edit
Delete
|
|
14031
|
9784
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11531
|
4
|
1774885219
|
1774885219
|
1774885218
|
1774885219
|
|
0
|
|
0
|
Edit
Delete
|
|
14032
|
9785
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11532
|
4
|
1774885519
|
1774885519
|
1774885518
|
1774885519
|
|
0
|
|
0
|
Edit
Delete
|
|
14033
|
9786
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11533
|
4
|
1774885819
|
1774885819
|
1774885818
|
1774885819
|
|
0
|
|
0
|
Edit
Delete
|
|
14034
|
9787
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11534
|
4
|
1774886119
|
1774886119
|
1774886118
|
1774886119
|
|
0
|
|
0
|
Edit
Delete
|
|
14035
|
9788
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11535
|
4
|
1774886419
|
1774886419
|
1774886418
|
1774886419
|
|
0
|
|
0
|
Edit
Delete
|
|
14036
|
9789
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11536
|
4
|
1774886719
|
1774886719
|
1774886718
|
1774886719
|
|
0
|
|
0
|
Edit
Delete
|
|
14037
|
9790
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11537
|
4
|
1774887019
|
1774887019
|
1774887018
|
1774887019
|
|
0
|
|
0
|
Edit
Delete
|
|
14038
|
9791
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11538
|
4
|
1774887319
|
1774887319
|
1774887318
|
1774887319
|
|
0
|
|
0
|
Edit
Delete
|
|
14039
|
9792
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11539
|
4
|
1774887619
|
1774887619
|
1774887618
|
1774887619
|
|
0
|
|
0
|
Edit
Delete
|
|
14040
|
9793
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11540
|
4
|
1774887919
|
1774887919
|
1774887918
|
1774887919
|
|
0
|
|
0
|
Edit
Delete
|
|
14041
|
9794
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11541
|
4
|
1774888219
|
1774888219
|
1774888218
|
1774888219
|
|
0
|
|
0
|
Edit
Delete
|
|
14042
|
9795
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11542
|
4
|
1774888519
|
1774888519
|
1774888518
|
1774888519
|
|
0
|
|
0
|
Edit
Delete
|
|
14043
|
9796
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11543
|
4
|
1774888819
|
1774888819
|
1774888818
|
1774888819
|
|
0
|
|
0
|
Edit
Delete
|
|
14044
|
9797
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
生产环境健康检查
|
1
|
name: Health Check
"on":
schedule:
name: Health Check
"on":
schedule:
# 每 5 分钟检查一次
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
health-check:
name: 生产环境健康检查
runs-on: ubuntu-latest
if: github.repository == 'your-org/juhi' # 替换为实际仓库
steps:
- id: api-health
name: API 健康检查
run: |
RESPONSE=$(curl -sf https://juhi.example.com/v1/health || echo '{"status":"error"}')
echo "response=$RESPONSE" >> $GITHUB_OUTPUT
STATUS=$(echo $RESPONSE | jq -r '.status // "error"')
if [ "$STATUS" != "ok" ]; then
echo "API 健康检查失败"
exit 1
fi
echo "API 健康检查通过"
- name: 前端可访问性检查
run: |
HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" https://juhi.example.com/)
if [ "$HTTP_STATUS" != "200" ]; then
echo "前端返回 HTTP $HTTP_STATUS"
exit 1
fi
echo "前端可访问性检查通过"
- name: SSL 证书检查
run: |
EXPIRY_DATE=$(echo | openssl s_client -servername juhi.example.com -connect juhi.example.com:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "SSL 证书剩余 $DAYS_LEFT 天"
if [ $DAYS_LEFT -lt 7 ]; then
echo "::warning::SSL 证书将在 $DAYS_LEFT 天后过期!"
fi
if [ $DAYS_LEFT -lt 0 ]; then
echo "SSL 证书已过期"
exit 1
fi
- name: 响应时间检查
run: |
RESPONSE_TIME=$(curl -so /dev/null -w "%{time_total}" https://juhi.example.com/v1/health)
echo "API 响应时间: ${RESPONSE_TIME}s"
# 响应时间超过 5 秒告警
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "::warning::API 响应时间过长: ${RESPONSE_TIME}s"
fi
- if: failure()
name: Slack 通知(失败时)
uses: 8398a7/action-slack@v3
with:
fields: repo,message,commit,author,action,eventName,workflow
status: ${{ job.status }}
text: "\U0001F6A8 生产环境健康检查失败!请立即检查。"
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
...
|
health-check
|
null
|
["ubuntu-latest"]
|
11544
|
4
|
1774889119
|
1774889119
|
1774889118
|
1774889119
|
|
0
|
|
0
|
Edit
Delete
|