Skip to content

🚀 GitHub Actions CI/CD

GitHub Actions 为 lzt 项目提供了完整的 CI/CD 解决方案,实现自动化构建、测试、部署和发布。

🎯 CI/CD 流程设计

工作流程概览

分支策略

  • main: 生产分支,触发生产部署
  • develop: 开发分支,触发预发布部署
  • feature/*: 功能分支,只运行测试和构建
  • hotfix/*: 热修复分支,快速修复生产问题

📋 基础工作流配置

主要构建流程

yaml
# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

env:
  GO_VERSION: '1.24.3'
  
jobs:
  lint:
    name: Code Linting
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}
        
    - name: Cache Go modules
      uses: actions/cache@v3
      with:
        path: ~/go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
          
    - name: Install dependencies
      run: go mod download
      
    - name: Run golangci-lint
      uses: golangci/golangci-lint-action@v3
      with:
        version: latest
        args: --timeout=5m
        
    - name: Check formatting
      run: |
        if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
          echo "Go code is not formatted:"
          gofmt -s -l .
          exit 1
        fi
        
    - name: Check imports
      run: |
        go install golang.org/x/tools/cmd/goimports@latest
        if [ "$(goimports -l . | wc -l)" -gt 0 ]; then
          echo "Go imports are not formatted:"
          goimports -l .
          exit 1
        fi

  test:
    name: Unit Tests
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}
        
    - name: Cache Go modules
      uses: actions/cache@v3
      with:
        path: ~/go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-
          
    - name: Install dependencies
      run: go mod download
      
    - name: Run tests with coverage
      run: |
        go test -race -coverprofile=coverage.out -covermode=atomic ./...
        
    - name: Check coverage threshold
      run: |
        COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
        echo "Coverage: $COVERAGE%"
        if (( $(echo "$COVERAGE < 80" | bc -l) )); then
          echo "Coverage $COVERAGE% is below required 80%"
          exit 1
        fi
        
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.out
        flags: unittests
        name: codecov-umbrella

  integration-test:
    name: Integration Tests
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: ledger_test
        ports:
          - 3306:3306
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
          
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}
        
    - name: Wait for MySQL
      run: |
        while ! mysqladmin ping -h127.0.0.1 -P3306 -uroot -ppassword --silent; do
          sleep 1
        done
        
    - name: Run integration tests
      run: go test -tags=integration -v ./...
      env:
        DB_HOST: 127.0.0.1
        DB_PORT: 3306
        DB_USER: root
        DB_PASSWORD: password
        DB_NAME: ledger_test

  security:
    name: Security Scan
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}
        
    - name: Run Gosec Security Scanner
      uses: securecodewarrior/github-action-gosec@master
      with:
        args: '-fmt sarif -out gosec-results.sarif ./...'
        
    - name: Upload SARIF file
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: gosec-results.sarif

  build:
    name: Build Application
    runs-on: ubuntu-latest
    needs: [lint, test]
    strategy:
      matrix:
        os: [linux, darwin, windows]
        arch: [amd64, arm64]
        exclude:
          - os: windows
            arch: arm64
            
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}
        
    - name: Build binary
      run: |
        GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build \
          -ldflags="-X main.version=${{ github.sha }}" \
          -o lzt-${{ matrix.os }}-${{ matrix.arch }} \
          ./cmd/lzt
          
    - name: Upload build artifact
      uses: actions/upload-artifact@v3
      with:
        name: lzt-${{ matrix.os }}-${{ matrix.arch }}
        path: lzt-${{ matrix.os }}-${{ matrix.arch }}

依赖更新自动化

yaml
# .github/workflows/dependency-update.yml
name: Dependency Update

on:
  schedule:
    - cron: '0 2 * * 1'  # 每周一凌晨2点
  workflow_dispatch:

jobs:
  update-dependencies:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.24.3'
        
    - name: Update Go modules
      run: |
        go get -u ./...
        go mod tidy
        
    - name: Run tests
      run: go test ./...
      
    - name: Create Pull Request
      uses: peter-evans/create-pull-request@v5
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        commit-message: 'chore: update dependencies'
        title: 'Automated dependency update'
        body: |
          This PR updates Go module dependencies to their latest versions.
          
          Please review the changes and ensure all tests pass before merging.
        branch: dependency-update
        delete-branch: true

🔄 部署流程

生产部署

yaml
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]

jobs:
  deploy:
    name: Deploy Application
    runs-on: ubuntu-latest
    environment: production
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.24.3'
        
    - name: Build for production
      run: |
        CGO_ENABLED=0 GOOS=linux go build \
          -ldflags="-w -s -X main.version=${{ github.ref_name }}" \
          -o lzt ./cmd/lzt
          
    - name: Build Docker image
      run: |
        docker build -t lzt:${{ github.sha }} .
        docker tag lzt:${{ github.sha }} lzt:latest
        
    - name: Login to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
        
    - name: Push Docker image
      run: |
        docker tag lzt:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }}
        docker tag lzt:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
        docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
        docker push ghcr.io/${{ github.repository }}:latest
        
    - name: Deploy to production
      run: |
        echo "Deploying to production environment..."
        # 部署脚本或 kubectl 命令
        
    - name: Health check
      run: |
        sleep 30
        curl -f http://production-endpoint/health || exit 1
        
    - name: Notify deployment
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        channel: '#deployments'
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}
      if: always()

预发布部署

yaml
# .github/workflows/staging.yml
name: Deploy to Staging

on:
  push:
    branches: [ develop ]

jobs:
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    environment: staging
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Deploy to staging
      run: |
        echo "Deploying to staging environment..."
        # 部署到预发布环境
        
    - name: Run E2E tests
      run: |
        echo "Running end-to-end tests..."
        # 运行端到端测试

🏷️ 自动发布

语义化版本发布

yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.24.3'
        
    - name: Run GoReleaser
      uses: goreleaser/goreleaser-action@v4
      with:
        distribution: goreleaser
        version: latest
        args: release --clean
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

GoReleaser 配置

yaml
# .goreleaser.yml
before:
  hooks:
    - go mod tidy
    - go generate ./...

builds:
  - main: ./cmd/lzt
    binary: lzt
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - darwin
      - windows
    goarch:
      - amd64
      - arm64
    ignore:
      - goos: windows
        goarch: arm64
    ldflags:
      - -s -w
      - -X main.version={{.Version}}
      - -X main.commit={{.Commit}}
      - -X main.date={{.Date}}

archives:
  - format: tar.gz
    name_template: >-
      {{ .ProjectName }}_
      {{- title .Os }}_
      {{- if eq .Arch "amd64" }}x86_64
      {{- else if eq .Arch "386" }}i386
      {{- else }}{{ .Arch }}{{ end }}
    format_overrides:
      - goos: windows
        format: zip

checksum:
  name_template: 'checksums.txt'

snapshot:
  name_template: "{{ incpatch .Version }}-next"

changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - '^chore:'

release:
  github:
    owner: FixIterate
    name: lzt
  draft: false
  prerelease: auto

🧪 测试自动化

E2E 测试流程

yaml
# .github/workflows/e2e.yml
name: E2E Tests

on:
  pull_request:
    paths-ignore:
      - 'docs/**'
      - '*.md'

jobs:
  e2e-test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.24.3'
        
    - name: Build application
      run: go build -o lzt ./cmd/lzt
      
    - name: Run E2E tests
      run: |
        # 启动测试环境
        docker-compose -f docker-compose.test.yml up -d
        
        # 等待服务启动
        sleep 30
        
        # 运行E2E测试
        go test -tags=e2e ./tests/e2e/...
        
    - name: Cleanup
      run: docker-compose -f docker-compose.test.yml down
      if: always()

性能测试

yaml
# .github/workflows/performance.yml
name: Performance Tests

on:
  schedule:
    - cron: '0 4 * * *'  # 每天凌晨4点
  workflow_dispatch:

jobs:
  benchmark:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.24.3'
        
    - name: Run benchmarks
      run: |
        go test -bench=. -benchmem -count=5 ./... > benchmark-results.txt
        
    - name: Store benchmark result
      uses: benchmark-action/github-action-benchmark@v1
      with:
        tool: 'go'
        output-file-path: benchmark-results.txt
        github-token: ${{ secrets.GITHUB_TOKEN }}
        auto-push: true
        comment-on-alert: true
        alert-threshold: '150%'

🔒 安全配置

Secrets 管理

yaml
# 在 GitHub Repository Settings > Secrets 中配置
secrets:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
  DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
  PRODUCTION_SSH_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}

安全扫描集成

yaml
# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
      
    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        scan-type: 'fs'
        scan-ref: '.'
        format: 'sarif'
        output: 'trivy-results.sarif'
        
    - name: Upload Trivy scan results
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: 'trivy-results.sarif'
        
    - name: Run Snyk security scan
      uses: snyk/actions/golang@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

📊 监控和通知

状态监控

yaml
# .github/workflows/health-check.yml
name: Health Check

on:
  schedule:
    - cron: '*/15 * * * *'  # 每15分钟检查一次

jobs:
  health-check:
    runs-on: ubuntu-latest
    steps:
    - name: Check production health
      run: |
        if ! curl -f https://api.lzt.com/health; then
          echo "Health check failed"
          exit 1
        fi
        
    - name: Notify on failure
      uses: 8398a7/action-slack@v3
      with:
        status: failure
        channel: '#alerts'
        text: 'Production health check failed!'
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}
      if: failure()

🔔 PR 检查通知系统

项目集成了智能的 PR 检查通知系统,基于 GitHub Check Suite 机制,当 Pull Request 的所有检查(包括外部服务)完成后自动发送通知,显著提高审核效率。

系统架构

核心特性

  • Check Suite 触发: 基于 GitHub 原生 Check Suite 机制,真正等待所有检查完成
  • 外部服务支持: 自动包含 Cloudflare Pages、Netlify、Vercel 等外部服务状态
  • 独立工作流: 使用独立的 pr-ready-notification.yml,不影响 CI 流程复杂度
  • 多渠道通知: 同时支持 GitHub 评论和 Webhook 外部通知(钉钉、Slack 等)
  • 智能状态汇总: 提供详细的检查结果表格和状态统计
  • 签名验证: 支持 HMAC-SHA256 签名验证确保 Webhook 安全
  • 简洁高效: 逻辑简化,专注核心通知功能

配置方式

快速配置(推荐)

使用 GitHub CLI 快速配置通知系统:

bash
# 钉钉机器人配置示例
gh variable set WEBHOOK_URL --body "https://oapi.dingtalk.com/robot/send?access_token=your_token"
gh secret set WEBHOOK_SECRET --body "SECyour_secret_key"

# Slack Webhook 配置示例
gh variable set WEBHOOK_URL --body "https://hooks.slack.com/services/T00/B00/XXX"
gh secret set WEBHOOK_SECRET --body "your_slack_signing_secret"

# 查看配置
gh variable list
gh secret list

配置说明

Repository Variables(可选):

yaml
# 外部 webhook 通知端点
WEBHOOK_URL: "https://your-webhook-endpoint.com/notifications"

Repository Secrets(可选):

yaml
# Webhook 签名验证密钥(HMAC-SHA256)
WEBHOOK_SECRET: "your-webhook-secret-key"

注意:

  • 如果不配置 WEBHOOK_URL,系统只发送 GitHub 评论通知
  • 如果配置了 WEBHOOK_URL 但不配置 WEBHOOK_SECRET,发送未签名的 webhook

通知内容示例

GitHub 评论格式

当检查完成后,系统会在 PR 中创建或更新评论:

markdown
## 🔍 PR 检查状态汇总

**整体状态**: ✅ 可以审核  
**检查完成时间**: 2024-01-20 10:30:00 UTC  
**总检查数**: 2

### 检查结果详情

| 检查项 | 状态 | 结果 | 链接 |
|--------|------|------|------|
| Test & Quality Check | ✅ | 成功 | [查看详情](https://github.com/repo/actions/runs/123) |
| Check Conventional Commits | ✅ | 成功 | [查看详情](https://github.com/repo/actions/runs/124) |

---
*🤖 此评论由 PR 检查通知系统自动生成*

Webhook 通知格式

系统会发送结构化的 JSON 数据到配置的端点:

json
{
  "event": "pr_checks_completed",
  "timestamp": "2024-01-20T10:30:00Z",
  "pr": {
    "number": 123,
    "title": "feat: 添加新功能",
    "author": "username",
    "head_branch": "feat/new-feature",
    "base_branch": "master",
    "sha": "abc123...",
    "url": "https://github.com/repo/pull/123"
  },
  "checks": {
    "total": 2,
    "passed": 2,
    "failed": 0,
    "skipped": 0,
    "overall_status": "success",
    "details": [...]
  },
  "summary": {
    "can_review": true,
    "needs_fixes": false,
    "message": "PR 检查全部通过,可以开始审核"
  }
}

安全特性

  • 签名验证: 支持 HMAC-SHA256 签名验证 webhook 请求
  • 权限最小化: 工作流仅使用必要的最小权限
  • 错误处理: 完善的错误处理确保通知失败不影响 PR 流程

详细配置和使用说明请参考:PR 通知系统文档

📚 相关资源

项目文档

外部参考


💡 CI/CD 建议: CI/CD 流程应该快速、可靠、可重复。投资于良好的测试覆盖率和自动化流程,可以显著提高开发效率和代码质量。

基于 MIT 许可证发布