Skip to content

CI/CD 构建与部署

预计阅读 20 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0) · 微信开发者工具:稳定版 1.06+

持续集成和部署是小游戏开发效率的关键。本章讲解如何搭建自动构建、上传的 CI/CD 流水线,让每一次 git push 都能自动产出可测试的小游戏版本。


微信开发者工具 CLI 上传

微信提供了命令行上传能力,需要配合 CI 密钥使用:

bash
# 开发者工具 CLI 上传命令(V2 子命令语法)
# 开发者工具路径 + upload 子命令 + 项目路径 + 版本号 + 描述
"/Applications/wechatwebdevtools.app/Contents/MacOS/cli" \
  upload \
  --project /path/to/minigame \
  --version 1.0.0 \
  --desc "CI 自动构建"

获取 CI 密钥

  1. 登录 微信公众平台
  2. 进入「开发 → 开发管理 → 开发设置」。
  3. 生成「小程序代码上传密钥」,下载 private.xxx.key
  4. 将 IP 白名单设置为 CI 服务器的出口 IP。

GitHub Actions 完整配置

项目结构

minigame/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── minigame/
│   ├── game.js
│   └── game.json
├── scripts/
│   └── ci-upload.js     # 上传脚本
├── package.json
└── project.config.json

Workflow 配置

.github/workflows/deploy.yml

yaml
name: Deploy WeChat Mini Game

on:
  push:
    branches: [main, release/*]
  workflow_dispatch:

jobs:
  build-and-upload:
    runs-on: macos-latest # 微信开发者工具仅支持 macOS/Windows
    timeout-minutes: 20

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 9

      - name: Install dependencies
        run: pnpm install

      - name: Build game
        run: |
          # 根据引擎调整构建命令
          # Cocos Creator:
          # /Applications/CocosCreator.app/Contents/MacOS/CocosCreator \
          #   --project ./ --build "platform=wechatgame;buildPath=dist"
          #
          # LayaAir:
          # npx layaair2-cmd publish -p wechat-game -o dist
          #
          # 原生 JS:
          pnpm build

      - name: Bump version
        id: version
        run: |
          # 基于时间戳生成版本号
          VERSION="1.0.$(date +%Y%m%d%H%M)"
          echo "version=$VERSION" >> $GITHUB_OUTPUT

      - name: Upload to WeChat
        env:
          WECHAT_APPID: ${{ secrets.WECHAT_APPID }}
          WECHAT_PRIVATE_KEY: ${{ secrets.WECHAT_PRIVATE_KEY }}
        run: node scripts/ci-upload.js --version ${{ steps.version.outputs.version }}

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: minigame-build-${{ steps.version.outputs.version }}
          path: dist/
          retention-days: 30

CI 上传脚本

scripts/ci-upload.js

javascript
const { execSync } = require('child_process')
const fs = require('fs')
const path = require('path')

const APPID = process.env.WECHAT_APPID
const PROJECT_PATH = path.resolve(__dirname, '..', 'dist')

async function main() {
  // 1. 验证必要环境变量
  if (!APPID) throw new Error('缺少 WECHAT_APPID 环境变量')
  if (!process.env.WECHAT_PRIVATE_KEY) throw new Error('缺少 WECHAT_PRIVATE_KEY')

  // 2. 写入 CI 密钥文件
  const keyDir = path.resolve(__dirname, '..', '.ci-keys')
  fs.mkdirSync(keyDir, { recursive: true })
  fs.writeFileSync(path.join(keyDir, `private.${APPID}.key`), process.env.WECHAT_PRIVATE_KEY)

  // 3. 确认 project.config.json 存在
  const projectConfig = path.join(PROJECT_PATH, 'project.config.json')
  if (!fs.existsSync(projectConfig)) {
    throw new Error('未找到 project.config.json,请确认构建产出了该文件')
  }

  // 4. 获取当前版本号
  const version = process.argv.includes('--version')
    ? process.argv[process.argv.indexOf('--version') + 1]
    : 'dev-' + Date.now()

  console.log(`🚀 上传版本: ${version}`)

  // 5. 调用微信 CLI 上传
  const cliPath = '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
  // Windows 路径:
  // const cliPath = 'C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat'

  try {
    execSync(
      `"${cliPath}" upload --project "${PROJECT_PATH}" --version "${version}" --desc "CI 自动构建 #${process.env.GITHUB_RUN_NUMBER || 'local'}"`,
      { stdio: 'inherit', timeout: 5 * 60 * 1000 }
    )
    console.log('✅ 上传成功')
  } catch (err) {
    console.error('❌ 上传失败', err.message)
    process.exit(1)
  }
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})

版本号自动管理

typescript
// 版本号工具:scripts/version.js
import { execSync } from 'node:child_process'
import * as fs from 'node:fs'

const versionType = process.argv[2] || 'patch'

// 从 package.json 读取当前版本
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
const [major, minor, patch] = pkg.version.split('.').map(Number)

let newVersion: string
switch (versionType) {
  case 'major':
    newVersion = `${major + 1}.0.0`
    break
  case 'minor':
    newVersion = `${major}.${minor + 1}.0`
    break
  default:
    newVersion = `${major}.${minor}.${patch + 1}`
}

pkg.version = newVersion
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n')

// Git 提交版本号更新
execSync(`git add package.json`)
execSync(`git commit -m "release: v${newVersion}"`)
execSync(`git tag v${newVersion}`)

console.log(`📦 版本号已更新: v${newVersion}`)

回滚策略

bash
# 紧急回滚到上一个稳定版本
# 1. 在微信公众平台 → 版本管理 → 找到上一个稳定版本
# 2. 点击「取消发布」撤销当前有问题的版本
# 3. 重新发布上一个版本

# 也可以直接切回 git tag
git checkout v1.0.5
git push origin HEAD:main --force  # 极端情况下强制回滚主线
CI/CD 最佳实践
  1. 分支策略main 分支自动上传为体验版,release/* 分支上传为正式版。
  2. 构建缓存 — 将 node_modules、Cocos Creator 引擎目录等加入 GitHub Actions cache。
  3. 密钥安全 — CI 密钥、微信 key 等敏感信息使用 GitHub Secrets 管理,绝不写入代码仓库
  4. 通知机制 — 构建成功/失败时发送企业微信或钉钉通知。
  5. 构件保留 — 每次构建产物保留 30 天,便于回滚和问题排查。

多云部署(CDN 资源)

yaml
# .github/workflows/deploy-cdn.yml
# 将静态资源部署到 CDN(配合热更新使用)

name: Deploy Assets to CDN

on:
  push:
    paths:
      - 'assets/**'
      - 'public/**'

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9

      - name: Install dependencies
        run: pnpm install

      - name: Build assets
        run: pnpm run build:assets # 根据项目实际构建脚本调整

      - name: Generate manifest
        run: node scripts/generateManifest.js

      - name: Upload to Tencent COS
        uses: tencentyun/cos-action@v1
        with:
          secret_id: ${{ secrets.TENCENT_SECRET_ID }}
          secret_key: ${{ secrets.TENCENT_SECRET_KEY }}
          cos_bucket: game-assets-1250000000
          cos_region: ap-guangzhou
          local_path: dist/assets
          remote_path: /game/assets

      - name: Purge CDN cache
        env:
          TENCENT_SECRET_ID: ${{ secrets.TENCENT_SECRET_ID }}
          TENCENT_SECRET_KEY: ${{ secrets.TENCENT_SECRET_KEY }}
        run: node scripts/refreshCdn.js

CDN 刷新脚本 scripts/refreshCdn.js(使用腾讯云 SDK 生成签名后调用):

javascript
// scripts/refreshCdn.js
const tencentcloud = require('tencentcloud-sdk-nodejs')
const CdnClient = tencentcloud.cdn.v20180606.Client

const client = new CdnClient({
  credential: {
    secretId: process.env.TENCENT_SECRET_ID,
    secretKey: process.env.TENCENT_SECRET_KEY,
  },
  region: '',
  profile: { signMethod: 'TC3-HMAC-SHA256' },
})

client.RefreshCdnUrl({
  Urls: ['https://cdn.example.com/game/manifest.json'],
}).then(() => console.log('CDN 刷新成功'))
  .catch((err) => {
    console.error('CDN 刷新失败', err)
    process.exit(1)
  })

📝 课后练习

练习 1:GitHub Actions 缓存优化

题目: 为示例 workflow 增加 actions/cache,缓存 node_modules 与 Cocos 引擎目录,减少构建时间。

参考答案要点: 使用 hashFiles('pnpm-lock.yaml') 作为缓存 key;路径包含 ~/.cocos 或引擎安装目录。

练习 2:版本号策略

题目: 为什么建议 main 自动上传体验版、release/* 上传正式版?会带来什么风险?

参考答案: 避免未经验证的代码直接进入正式版审核;风险是分支策略需要团队遵守,且误推 tag 可能导致非预期发布。

📚 相关阅读

用心学习,持续实践