1. 打包配置

1.1 manifest.json配置

{
  "name": "MyUniApp",
  "appid": "__UNI__XXXXXXX",
  "description": "我的UniApp应用",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "networkTimeout": {
    "request": 60000,
    "connectSocket": 60000,
    "uploadFile": 60000,
    "downloadFile": 60000
  },
  "debug": false,
  "uniStatistics": {
    "enable": false
  },
  "app-plus": {
    "usingComponents": true,
    "nvueStyleCompiler": "uni-app",
    "compilerVersion": 3,
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "waiting": true,
      "autoclose": true,
      "delay": 0
    },
    "modules": {},
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />",
          "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\" />",
          "<uses-permission android:name=\"android.permission.VIBRATE\" />",
          "<uses-permission android:name=\"android.permission.READ_LOGS\" />",
          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />",
          "<uses-feature android:name=\"android.hardware.camera.autofocus\" />",
          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />",
          "<uses-permission android:name=\"android.permission.CAMERA\" />",
          "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />",
          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />",
          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\" />",
          "<uses-permission android:name=\"android.permission.WAKE_LOCK\" />",
          "<uses-permission android:name=\"android.permission.FLASHLIGHT\" />",
          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\" />"
        ],
        "abiFilters": ["armeabi-v7a", "arm64-v8a"],
        "targetSdkVersion": 30,
        "minSdkVersion": 21
      },
      "ios": {
        "deploymentTarget": "9.0",
        "dSYMs": false
      },
      "sdkConfigs": {
        "ad": {},
        "oauth": {},
        "payment": {},
        "push": {},
        "share": {},
        "statics": {
          "umeng": {
            "appkey_ios": "",
            "appkey_android": "",
            "channel": "umeng"
          }
        }
      },
      "icons": {
        "android": {
          "hdpi": "unpackage/res/icons/72x72.png",
          "xhdpi": "unpackage/res/icons/96x96.png",
          "xxhdpi": "unpackage/res/icons/144x144.png",
          "xxxhdpi": "unpackage/res/icons/192x192.png"
        },
        "ios": {
          "appstore": "unpackage/res/icons/1024x1024.png",
          "ipad": {
            "app": "unpackage/res/icons/76x76.png",
            "app@2x": "unpackage/res/icons/152x152.png",
            "notification": "unpackage/res/icons/20x20.png",
            "notification@2x": "unpackage/res/icons/40x40.png",
            "proapp@2x": "unpackage/res/icons/167x167.png",
            "settings": "unpackage/res/icons/29x29.png",
            "settings@2x": "unpackage/res/icons/58x58.png",
            "spotlight": "unpackage/res/icons/40x40.png",
            "spotlight@2x": "unpackage/res/icons/80x80.png"
          },
          "iphone": {
            "app@2x": "unpackage/res/icons/120x120.png",
            "app@3x": "unpackage/res/icons/180x180.png",
            "notification@2x": "unpackage/res/icons/40x40.png",
            "notification@3x": "unpackage/res/icons/60x60.png",
            "settings@2x": "unpackage/res/icons/58x58.png",
            "settings@3x": "unpackage/res/icons/87x87.png",
            "spotlight@2x": "unpackage/res/icons/80x80.png",
            "spotlight@3x": "unpackage/res/icons/120x120.png"
          }
        }
      }
    }
  },
  "quickapp": {},
  "mp-weixin": {
    "appid": "wxXXXXXXXXXXXXXXXX",
    "setting": {
      "urlCheck": false,
      "es6": true,
      "enhance": true,
      "postcss": true,
      "preloadBackgroundData": false,
      "minified": true,
      "newFeature": false,
      "coverView": true,
      "nodeModules": false,
      "autoAudits": false,
      "showShadowRootInWxmlPanel": true,
      "scopeDataCheck": false,
      "uglifyFileName": false,
      "checkInvalidKey": true,
      "checkSiteMap": true,
      "uploadWithSourceMap": true,
      "compileHotReLoad": false,
      "lazyloadPlaceholderEnable": false,
      "useMultiFrameRuntime": true,
      "useApiHook": true,
      "useApiHostProcess": true,
      "babelSetting": {
        "ignore": [],
        "disablePlugins": [],
        "outputPath": ""
      },
      "enableEngineNative": false,
      "useIsolateContext": true,
      "userConfirmedBundleSwitch": false,
      "packNpmManually": false,
      "packNpmRelationList": [],
      "minifyWXSS": true,
      "disableUseStrict": false,
      "minifyWXML": true,
      "showES6CompileOption": false,
      "useCompilerPlugins": false
    },
    "usingComponents": true,
    "permission": {
      "scope.userLocation": {
        "desc": "你的位置信息将用于小程序位置接口的效果展示"
      }
    },
    "requiredPrivateInfos": ["getLocation"]
  },
  "mp-alipay": {
    "usingComponents": true
  },
  "mp-baidu": {
    "usingComponents": true
  },
  "mp-toutiao": {
    "usingComponents": true
  },
  "uniStatistics": {
    "enable": false
  },
  "h5": {
    "template": "index.html",
    "router": {
      "mode": "hash",
      "base": "/"
    },
    "optimization": {
      "treeShaking": {
        "enable": true
      }
    },
    "title": "MyUniApp",
    "devServer": {
      "https": false,
      "port": 8080,
      "disableHostCheck": true
    }
  }
}

1.2 环境配置

// config/env.js
const ENV_CONFIG = {
  development: {
    API_BASE_URL: 'http://localhost:3000/api',
    WS_BASE_URL: 'ws://localhost:3000',
    APP_NAME: 'MyApp(开发)',
    DEBUG: true,
    LOG_LEVEL: 'debug'
  },
  
  testing: {
    API_BASE_URL: 'https://test-api.example.com/api',
    WS_BASE_URL: 'wss://test-api.example.com',
    APP_NAME: 'MyApp(测试)',
    DEBUG: true,
    LOG_LEVEL: 'info'
  },
  
  staging: {
    API_BASE_URL: 'https://staging-api.example.com/api',
    WS_BASE_URL: 'wss://staging-api.example.com',
    APP_NAME: 'MyApp(预发布)',
    DEBUG: false,
    LOG_LEVEL: 'warn'
  },
  
  production: {
    API_BASE_URL: 'https://api.example.com/api',
    WS_BASE_URL: 'wss://api.example.com',
    APP_NAME: 'MyApp',
    DEBUG: false,
    LOG_LEVEL: 'error'
  }
}

// 获取当前环境
function getCurrentEnv() {
  // #ifdef H5
  const hostname = window.location.hostname
  if (hostname === 'localhost' || hostname === '127.0.0.1') {
    return 'development'
  } else if (hostname.includes('test')) {
    return 'testing'
  } else if (hostname.includes('staging')) {
    return 'staging'
  } else {
    return 'production'
  }
  // #endif
  
  // #ifdef APP-PLUS
  // 根据打包配置或其他方式判断环境
  return process.env.NODE_ENV === 'production' ? 'production' : 'development'
  // #endif
  
  // #ifdef MP
  // 小程序环境判断
  const accountInfo = wx.getAccountInfoSync()
  const envVersion = accountInfo.miniProgram.envVersion
  
  switch (envVersion) {
    case 'develop':
      return 'development'
    case 'trial':
      return 'testing'
    case 'release':
      return 'production'
    default:
      return 'development'
  }
  // #endif
}

// 获取环境配置
function getEnvConfig() {
  const env = getCurrentEnv()
  return ENV_CONFIG[env] || ENV_CONFIG.development
}

export { getCurrentEnv, getEnvConfig }
export default getEnvConfig()

1.3 构建脚本

// package.json
{
  "name": "my-uniapp",
  "version": "1.0.0",
  "description": "My UniApp Project",
  "main": "main.js",
  "scripts": {
    "serve": "npm run dev:h5",
    "build": "npm run build:h5",
    "build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
    "build:custom": "cross-env NODE_ENV=production uniapp-cli custom",
    "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
    "build:mp-360": "cross-env NODE_ENV=production UNI_PLATFORM=mp-360 vue-cli-service uni-build",
    "build:mp-alipay": "cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build",
    "build:mp-baidu": "cross-env NODE_ENV=production UNI_PLATFORM=mp-baidu vue-cli-service uni-build",
    "build:mp-kuaishou": "cross-env NODE_ENV=production UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build",
    "build:mp-lark": "cross-env NODE_ENV=production UNI_PLATFORM=mp-lark vue-cli-service uni-build",
    "build:mp-qq": "cross-env NODE_ENV=production UNI_PLATFORM=mp-qq vue-cli-service uni-build",
    "build:mp-toutiao": "cross-env NODE_ENV=production UNI_PLATFORM=mp-toutiao vue-cli-service uni-build",
    "build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
    "build:quickapp-native": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-native vue-cli-service uni-build",
    "build:quickapp-webview": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview vue-cli-service uni-build",
    "dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch",
    "dev:custom": "cross-env NODE_ENV=development uniapp-cli custom",
    "dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve",
    "dev:mp-360": "cross-env NODE_ENV=development UNI_PLATFORM=mp-360 vue-cli-service uni-build --watch",
    "dev:mp-alipay": "cross-env NODE_ENV=development UNI_PLATFORM=mp-alipay vue-cli-service uni-build --watch",
    "dev:mp-baidu": "cross-env NODE_ENV=development UNI_PLATFORM=mp-baidu vue-cli-service uni-build --watch",
    "dev:mp-kuaishou": "cross-env NODE_ENV=development UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build --watch",
    "dev:mp-lark": "cross-env NODE_ENV=development UNI_PLATFORM=mp-lark vue-cli-service uni-build --watch",
    "dev:mp-qq": "cross-env NODE_ENV=development UNI_PLATFORM=mp-qq vue-cli-service uni-build --watch",
    "dev:mp-toutiao": "cross-env NODE_ENV=development UNI_PLATFORM=mp-toutiao vue-cli-service uni-build --watch",
    "dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch",
    "dev:quickapp-native": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-native vue-cli-service uni-build --watch",
    "dev:quickapp-webview": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview vue-cli-service uni-build --watch",
    "info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js",
    "serve:quickapp-native": "node node_modules/@dcloudio/uni-quickapp-native/bin/serve.js",
    "test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest",
    "test:h5": "cross-env UNI_PLATFORM=h5 jest",
    "test:ios": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=ios jest",
    "test:mp-baidu": "cross-env UNI_PLATFORM=mp-baidu jest",
    "test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest",
    "lint": "eslint --ext .js,.vue src",
    "lint:fix": "eslint --ext .js,.vue src --fix",
    "build:all": "npm run build:h5 && npm run build:mp-weixin && npm run build:app-plus",
    "build:staging": "cross-env NODE_ENV=staging npm run build:all",
    "build:production": "cross-env NODE_ENV=production npm run build:all",
    "deploy:h5": "npm run build:h5 && node scripts/deploy-h5.js",
    "deploy:staging": "npm run build:staging && node scripts/deploy-staging.js",
    "deploy:production": "npm run build:production && node scripts/deploy-production.js"
  },
  "dependencies": {
    "@dcloudio/uni-app": "^2.0.0",
    "@dcloudio/uni-h5": "^2.0.0",
    "@dcloudio/uni-mp-alipay": "^2.0.0",
    "@dcloudio/uni-mp-baidu": "^2.0.0",
    "@dcloudio/uni-mp-qq": "^2.0.0",
    "@dcloudio/uni-mp-toutiao": "^2.0.0",
    "@dcloudio/uni-mp-weixin": "^2.0.0",
    "@dcloudio/uni-quickapp-native": "^2.0.0",
    "@dcloudio/uni-quickapp-webview": "^2.0.0",
    "@dcloudio/uni-stat": "^2.0.0",
    "core-js": "^3.6.5",
    "vue": "^2.6.11",
    "vuex": "^3.2.0"
  },
  "devDependencies": {
    "@dcloudio/types": "^2.3.4",
    "@dcloudio/uni-automator": "^2.0.0",
    "@dcloudio/uni-cli-shared": "^2.0.0",
    "@dcloudio/uni-migration": "^2.0.0",
    "@dcloudio/uni-template-compiler": "^2.0.0",
    "@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.0",
    "@dcloudio/vue-cli-plugin-uni": "^2.0.0",
    "@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.0",
    "@dcloudio/webpack-uni-mp-loader": "^2.0.0",
    "@dcloudio/webpack-uni-pages-loader": "^2.0.0",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "babel-plugin-import": "^1.11.0",
    "cross-env": "^7.0.2",
    "jest": "^25.4.0",
    "mini-types": "*",
    "postcss-comment": "^2.0.0",
    "vue-template-compiler": "^2.6.11"
  },
  "browserslist": [
    "Android >= 4.4",
    "ios >= 9"
  ],
  "uni-app": {
    "scripts": {}
  }
}

2. 各平台发布

2.1 H5发布

// scripts/deploy-h5.js
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const archiver = require('archiver')
const FTP = require('ftp')
const OSS = require('ali-oss')

class H5Deployer {
  constructor(config) {
    this.config = config
    this.distPath = path.resolve(__dirname, '../dist/build/h5')
  }
  
  // 部署到静态服务器
  async deployToStatic() {
    console.log('开始部署到静态服务器...')
    
    try {
      // 压缩文件
      const zipPath = await this.createZip()
      
      // 上传到服务器
      if (this.config.deployType === 'ftp') {
        await this.uploadByFTP(zipPath)
      } else if (this.config.deployType === 'oss') {
        await this.uploadByOSS()
      } else if (this.config.deployType === 'ssh') {
        await this.uploadBySSH(zipPath)
      }
      
      console.log('部署完成!')
    } catch (error) {
      console.error('部署失败:', error)
      process.exit(1)
    }
  }
  
  // 创建压缩包
  createZip() {
    return new Promise((resolve, reject) => {
      const zipPath = path.resolve(__dirname, '../dist/h5-dist.zip')
      const output = fs.createWriteStream(zipPath)
      const archive = archiver('zip', { zlib: { level: 9 } })
      
      output.on('close', () => {
        console.log(`压缩包创建完成: ${archive.pointer()} bytes`)
        resolve(zipPath)
      })
      
      archive.on('error', reject)
      archive.pipe(output)
      archive.directory(this.distPath, false)
      archive.finalize()
    })
  }
  
  // FTP上传
  uploadByFTP(zipPath) {
    return new Promise((resolve, reject) => {
      const ftp = new FTP()
      
      ftp.on('ready', () => {
        console.log('FTP连接成功')
        
        // 上传压缩包
        ftp.put(zipPath, path.basename(zipPath), (err) => {
          if (err) {
            reject(err)
            return
          }
          
          console.log('文件上传完成')
          
          // 解压文件(需要服务器支持)
          ftp.raw('SITE', 'UNZIP', path.basename(zipPath), (err, data) => {
            ftp.end()
            if (err) {
              console.warn('自动解压失败,请手动解压:', err.message)
            } else {
              console.log('文件解压完成')
            }
            resolve()
          })
        })
      })
      
      ftp.on('error', reject)
      
      ftp.connect({
        host: this.config.ftp.host,
        port: this.config.ftp.port || 21,
        user: this.config.ftp.username,
        password: this.config.ftp.password
      })
    })
  }
  
  // OSS上传
  async uploadByOSS() {
    const client = new OSS({
      region: this.config.oss.region,
      accessKeyId: this.config.oss.accessKeyId,
      accessKeySecret: this.config.oss.accessKeySecret,
      bucket: this.config.oss.bucket
    })
    
    console.log('开始上传到OSS...')
    
    // 获取所有文件
    const files = this.getAllFiles(this.distPath)
    
    // 并发上传
    const uploadPromises = files.map(async (file) => {
      const relativePath = path.relative(this.distPath, file)
      const ossPath = path.posix.join(this.config.oss.prefix || '', relativePath)
      
      try {
        await client.put(ossPath, file)
        console.log(`上传成功: ${ossPath}`)
      } catch (error) {
        console.error(`上传失败: ${ossPath}`, error)
        throw error
      }
    })
    
    await Promise.all(uploadPromises)
    console.log('OSS上传完成')
  }
  
  // SSH上传
  async uploadBySSH(zipPath) {
    const { NodeSSH } = require('node-ssh')
    const ssh = new NodeSSH()
    
    try {
      await ssh.connect({
        host: this.config.ssh.host,
        port: this.config.ssh.port || 22,
        username: this.config.ssh.username,
        password: this.config.ssh.password,
        privateKey: this.config.ssh.privateKey
      })
      
      console.log('SSH连接成功')
      
      // 上传压缩包
      await ssh.putFile(zipPath, `/tmp/${path.basename(zipPath)}`)
      console.log('文件上传完成')
      
      // 解压到目标目录
      const commands = [
        `cd ${this.config.ssh.targetPath}`,
        `rm -rf *`,
        `unzip -o /tmp/${path.basename(zipPath)}`,
        `rm /tmp/${path.basename(zipPath)}`
      ]
      
      for (const command of commands) {
        const result = await ssh.execCommand(command)
        if (result.code !== 0) {
          throw new Error(`命令执行失败: ${command}\n${result.stderr}`)
        }
      }
      
      console.log('文件部署完成')
    } finally {
      ssh.dispose()
    }
  }
  
  // 获取所有文件
  getAllFiles(dir) {
    const files = []
    
    function traverse(currentDir) {
      const items = fs.readdirSync(currentDir)
      
      for (const item of items) {
        const fullPath = path.join(currentDir, item)
        const stat = fs.statSync(fullPath)
        
        if (stat.isDirectory()) {
          traverse(fullPath)
        } else {
          files.push(fullPath)
        }
      }
    }
    
    traverse(dir)
    return files
  }
}

// 部署配置
const deployConfig = {
  deployType: 'oss', // ftp, oss, ssh
  
  ftp: {
    host: 'ftp.example.com',
    port: 21,
    username: 'username',
    password: 'password'
  },
  
  oss: {
    region: 'oss-cn-hangzhou',
    accessKeyId: process.env.OSS_ACCESS_KEY_ID,
    accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
    bucket: 'my-app-bucket',
    prefix: 'h5/'
  },
  
  ssh: {
    host: 'server.example.com',
    port: 22,
    username: 'root',
    password: process.env.SSH_PASSWORD,
    targetPath: '/var/www/html'
  }
}

// 执行部署
const deployer = new H5Deployer(deployConfig)
deployer.deployToStatic().catch(console.error)

2.2 小程序发布

// scripts/deploy-miniprogram.js
const fs = require('fs')
const path = require('path')
const ci = require('miniprogram-ci')

class MiniprogramDeployer {
  constructor(config) {
    this.config = config
  }
  
  // 微信小程序发布
  async deployWeixin() {
    console.log('开始发布微信小程序...')
    
    try {
      const project = new ci.Project({
        appid: this.config.weixin.appid,
        type: 'miniProgram',
        projectPath: path.resolve(__dirname, '../dist/build/mp-weixin'),
        privateKeyPath: this.config.weixin.privateKeyPath,
        ignores: ['node_modules/**/*']
      })
      
      // 上传代码
      const uploadResult = await ci.upload({
        project,
        version: this.config.version,
        desc: this.config.desc,
        setting: {
          es6: true,
          es7: true,
          minifyJS: true,
          minifyWXML: true,
          minifyWXSS: true,
          autoPrefixWXSS: true
        },
        onProgressUpdate: (progress) => {
          console.log(`上传进度: ${progress}%`)
        }
      })
      
      console.log('微信小程序上传成功:', uploadResult)
      
      // 提交审核(可选)
      if (this.config.submitAudit) {
        await this.submitWeixinAudit(project)
      }
      
    } catch (error) {
      console.error('微信小程序发布失败:', error)
      throw error
    }
  }
  
  // 提交微信小程序审核
  async submitWeixinAudit(project) {
    try {
      const submitResult = await ci.submit({
        project,
        version: this.config.version,
        desc: this.config.desc,
        setting: {
          es6: true,
          es7: true,
          minifyJS: true,
          minifyWXML: true,
          minifyWXSS: true
        }
      })
      
      console.log('提交审核成功:', submitResult)
    } catch (error) {
      console.error('提交审核失败:', error)
      throw error
    }
  }
  
  // 支付宝小程序发布
  async deployAlipay() {
    console.log('开始发布支付宝小程序...')
    
    // 支付宝小程序需要使用支付宝开发者工具的命令行工具
    const { execSync } = require('child_process')
    
    try {
      const distPath = path.resolve(__dirname, '../dist/build/mp-alipay')
      
      // 使用支付宝开发者工具命令行上传
      const command = `alipay-dev-tool upload --project ${distPath} --version ${this.config.version} --desc "${this.config.desc}"`
      
      execSync(command, { stdio: 'inherit' })
      console.log('支付宝小程序上传成功')
      
    } catch (error) {
      console.error('支付宝小程序发布失败:', error)
      throw error
    }
  }
  
  // 百度小程序发布
  async deployBaidu() {
    console.log('开始发布百度小程序...')
    
    const { execSync } = require('child_process')
    
    try {
      const distPath = path.resolve(__dirname, '../dist/build/mp-baidu')
      
      // 使用百度开发者工具命令行上传
      const command = `swan upload --project-path ${distPath} --version ${this.config.version} --desc "${this.config.desc}"`
      
      execSync(command, { stdio: 'inherit' })
      console.log('百度小程序上传成功')
      
    } catch (error) {
      console.error('百度小程序发布失败:', error)
      throw error
    }
  }
  
  // 字节跳动小程序发布
  async deployToutiao() {
    console.log('开始发布字节跳动小程序...')
    
    const { execSync } = require('child_process')
    
    try {
      const distPath = path.resolve(__dirname, '../dist/build/mp-toutiao')
      
      // 使用字节跳动开发者工具命令行上传
      const command = `tt-ide-cli upload --project-path ${distPath} --version ${this.config.version} --desc "${this.config.desc}"`
      
      execSync(command, { stdio: 'inherit' })
      console.log('字节跳动小程序上传成功')
      
    } catch (error) {
      console.error('字节跳动小程序发布失败:', error)
      throw error
    }
  }
  
  // 发布所有小程序
  async deployAll() {
    const platforms = this.config.platforms || ['weixin']
    
    for (const platform of platforms) {
      try {
        switch (platform) {
          case 'weixin':
            await this.deployWeixin()
            break
          case 'alipay':
            await this.deployAlipay()
            break
          case 'baidu':
            await this.deployBaidu()
            break
          case 'toutiao':
            await this.deployToutiao()
            break
          default:
            console.warn(`不支持的平台: ${platform}`)
        }
      } catch (error) {
        console.error(`${platform}平台发布失败:`, error)
        if (this.config.stopOnError) {
          throw error
        }
      }
    }
  }
}

// 发布配置
const deployConfig = {
  version: process.env.VERSION || '1.0.0',
  desc: process.env.DESC || '版本更新',
  platforms: ['weixin', 'alipay'],
  submitAudit: false,
  stopOnError: true,
  
  weixin: {
    appid: 'wxXXXXXXXXXXXXXXXX',
    privateKeyPath: path.resolve(__dirname, '../private.key')
  }
}

// 执行发布
const deployer = new MiniprogramDeployer(deployConfig)
deployer.deployAll().catch(console.error)

2.3 App发布

// scripts/deploy-app.js
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const axios = require('axios')
const FormData = require('form-data')

class AppDeployer {
  constructor(config) {
    this.config = config
  }
  
  // Android打包发布
  async deployAndroid() {
    console.log('开始Android打包发布...')
    
    try {
      // 1. 云端打包
      const apkPath = await this.buildAndroidCloud()
      
      // 2. 签名APK
      const signedApkPath = await this.signApk(apkPath)
      
      // 3. 上传到分发平台
      await this.uploadToDistribution(signedApkPath, 'android')
      
      // 4. 发送通知
      await this.sendNotification('Android版本发布成功')
      
      console.log('Android发布完成')
    } catch (error) {
      console.error('Android发布失败:', error)
      throw error
    }
  }
  
  // iOS打包发布
  async deployIOS() {
    console.log('开始iOS打包发布...')
    
    try {
      // 1. 云端打包
      const ipaPath = await this.buildIOSCloud()
      
      // 2. 上传到App Store Connect
      await this.uploadToAppStore(ipaPath)
      
      // 3. 上传到分发平台
      await this.uploadToDistribution(ipaPath, 'ios')
      
      // 4. 发送通知
      await this.sendNotification('iOS版本发布成功')
      
      console.log('iOS发布完成')
    } catch (error) {
      console.error('iOS发布失败:', error)
      throw error
    }
  }
  
  // 云端打包Android
  async buildAndroidCloud() {
    console.log('开始云端打包Android...')
    
    // 使用DCloud云端打包API
    const buildResult = await this.requestCloudBuild({
      platform: 'android',
      type: 'release',
      certificate: this.config.android.certificate,
      profile: this.config.android.profile
    })
    
    // 等待打包完成
    const apkPath = await this.waitForBuild(buildResult.buildId)
    console.log('Android打包完成:', apkPath)
    
    return apkPath
  }
  
  // 云端打包iOS
  async buildIOSCloud() {
    console.log('开始云端打包iOS...')
    
    const buildResult = await this.requestCloudBuild({
      platform: 'ios',
      type: 'release',
      certificate: this.config.ios.certificate,
      profile: this.config.ios.profile
    })
    
    const ipaPath = await this.waitForBuild(buildResult.buildId)
    console.log('iOS打包完成:', ipaPath)
    
    return ipaPath
  }
  
  // 请求云端打包
  async requestCloudBuild(options) {
    const response = await axios.post('https://ide.dcloud.net.cn/build', {
      appid: this.config.appid,
      platform: options.platform,
      type: options.type,
      certificate: options.certificate,
      profile: options.profile,
      version: this.config.version,
      versionCode: this.config.versionCode
    }, {
      headers: {
        'Authorization': `Bearer ${this.config.accessToken}`,
        'Content-Type': 'application/json'
      }
    })
    
    if (response.data.code !== 0) {
      throw new Error(`云端打包失败: ${response.data.message}`)
    }
    
    return response.data.data
  }
  
  // 等待打包完成
  async waitForBuild(buildId) {
    console.log('等待打包完成...')
    
    let attempts = 0
    const maxAttempts = 60 // 最多等待30分钟
    
    while (attempts < maxAttempts) {
      await new Promise(resolve => setTimeout(resolve, 30000)) // 等待30秒
      
      try {
        const response = await axios.get(`https://ide.dcloud.net.cn/build/${buildId}`, {
          headers: {
            'Authorization': `Bearer ${this.config.accessToken}`
          }
        })
        
        const build = response.data.data
        
        if (build.status === 'success') {
          // 下载打包文件
          return await this.downloadBuildFile(build.downloadUrl)
        } else if (build.status === 'failed') {
          throw new Error(`打包失败: ${build.error}`)
        }
        
        console.log(`打包进度: ${build.progress}%`)
      } catch (error) {
        console.error('查询打包状态失败:', error)
      }
      
      attempts++
    }
    
    throw new Error('打包超时')
  }
  
  // 下载打包文件
  async downloadBuildFile(downloadUrl) {
    const response = await axios({
      method: 'GET',
      url: downloadUrl,
      responseType: 'stream'
    })
    
    const fileName = path.basename(downloadUrl)
    const filePath = path.resolve(__dirname, '../dist', fileName)
    
    const writer = fs.createWriteStream(filePath)
    response.data.pipe(writer)
    
    return new Promise((resolve, reject) => {
      writer.on('finish', () => resolve(filePath))
      writer.on('error', reject)
    })
  }
  
  // 签名APK
  async signApk(apkPath) {
    console.log('开始签名APK...')
    
    const signedApkPath = apkPath.replace('.apk', '-signed.apk')
    
    const command = [
      'jarsigner',
      '-verbose',
      '-sigalg', 'SHA1withRSA',
      '-digestalg', 'SHA1',
      '-keystore', this.config.android.keystorePath,
      '-storepass', this.config.android.storePassword,
      '-keypass', this.config.android.keyPassword,
      '-signedjar', signedApkPath,
      apkPath,
      this.config.android.keyAlias
    ].join(' ')
    
    execSync(command, { stdio: 'inherit' })
    console.log('APK签名完成:', signedApkPath)
    
    return signedApkPath
  }
  
  // 上传到App Store
  async uploadToAppStore(ipaPath) {
    console.log('上传到App Store...')
    
    const command = [
      'xcrun', 'altool',
      '--upload-app',
      '--type', 'ios',
      '--file', ipaPath,
      '--username', this.config.ios.appleId,
      '--password', this.config.ios.appPassword
    ].join(' ')
    
    execSync(command, { stdio: 'inherit' })
    console.log('App Store上传完成')
  }
  
  // 上传到分发平台
  async uploadToDistribution(filePath, platform) {
    console.log(`上传到分发平台: ${platform}`)
    
    const form = new FormData()
    form.append('file', fs.createReadStream(filePath))
    form.append('platform', platform)
    form.append('version', this.config.version)
    form.append('desc', this.config.desc)
    
    const response = await axios.post(this.config.distribution.uploadUrl, form, {
      headers: {
        ...form.getHeaders(),
        'Authorization': `Bearer ${this.config.distribution.token}`
      },
      maxContentLength: Infinity,
      maxBodyLength: Infinity
    })
    
    if (response.data.code !== 0) {
      throw new Error(`分发平台上传失败: ${response.data.message}`)
    }
    
    console.log('分发平台上传完成:', response.data.data.downloadUrl)
  }
  
  // 发送通知
  async sendNotification(message) {
    if (this.config.notification.webhook) {
      try {
        await axios.post(this.config.notification.webhook, {
          text: message,
          timestamp: Date.now()
        })
      } catch (error) {
        console.error('发送通知失败:', error)
      }
    }
  }
  
  // 发布所有平台
  async deployAll() {
    const platforms = this.config.platforms || ['android']
    
    for (const platform of platforms) {
      try {
        if (platform === 'android') {
          await this.deployAndroid()
        } else if (platform === 'ios') {
          await this.deployIOS()
        }
      } catch (error) {
        console.error(`${platform}平台发布失败:`, error)
        if (this.config.stopOnError) {
          throw error
        }
      }
    }
  }
}

// 发布配置
const deployConfig = {
  appid: '__UNI__XXXXXXX',
  version: '1.0.0',
  versionCode: 100,
  desc: '版本更新',
  platforms: ['android', 'ios'],
  stopOnError: true,
  accessToken: process.env.DCLOUD_ACCESS_TOKEN,
  
  android: {
    certificate: 'android_certificate_id',
    profile: 'android_profile_id',
    keystorePath: path.resolve(__dirname, '../android.keystore'),
    storePassword: process.env.ANDROID_STORE_PASSWORD,
    keyPassword: process.env.ANDROID_KEY_PASSWORD,
    keyAlias: 'android_key'
  },
  
  ios: {
    certificate: 'ios_certificate_id',
    profile: 'ios_profile_id',
    appleId: process.env.APPLE_ID,
    appPassword: process.env.APPLE_APP_PASSWORD
  },
  
  distribution: {
    uploadUrl: 'https://api.distribution.com/upload',
    token: process.env.DISTRIBUTION_TOKEN
  },
  
  notification: {
    webhook: process.env.NOTIFICATION_WEBHOOK
  }
}

// 执行发布
const deployer = new AppDeployer(deployConfig)
deployer.deployAll().catch(console.error)

3. CI/CD集成

3.1 GitHub Actions

# .github/workflows/deploy.yml
name: Deploy UniApp

on:
  push:
    branches: [ main, develop ]
    tags: [ 'v*' ]
  pull_request:
    branches: [ main ]

env:
  NODE_VERSION: '16'
  DCLOUD_ACCESS_TOKEN: ${{ secrets.DCLOUD_ACCESS_TOKEN }}
  OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
  OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm run test
    
    - name: Run lint
      run: npm run lint

  build:
    needs: test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform: [h5, mp-weixin, app-plus]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Build ${{ matrix.platform }}
      run: npm run build:${{ matrix.platform }}
    
    - name: Upload build artifacts
      uses: actions/upload-artifact@v3
      with:
        name: build-${{ matrix.platform }}
        path: dist/build/${{ matrix.platform }}
        retention-days: 7

  deploy-h5:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Download build artifacts
      uses: actions/download-artifact@v3
      with:
        name: build-h5
        path: dist/build/h5
    
    - name: Deploy to OSS
      run: npm run deploy:h5
    
    - name: Notify deployment
      if: always()
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        text: 'H5部署完成'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

  deploy-miniprogram:
    needs: build
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Download build artifacts
      uses: actions/download-artifact@v3
      with:
        name: build-mp-weixin
        path: dist/build/mp-weixin
    
    - name: Deploy miniprogram
      run: |
        export VERSION=${GITHUB_REF#refs/tags/v}
        export DESC="Release $VERSION"
        node scripts/deploy-miniprogram.js
      env:
        WEIXIN_PRIVATE_KEY: ${{ secrets.WEIXIN_PRIVATE_KEY }}

  deploy-app:
    needs: build
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/v')
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Download build artifacts
      uses: actions/download-artifact@v3
      with:
        name: build-app-plus
        path: dist/build/app-plus
    
    - name: Deploy app
      run: |
        export VERSION=${GITHUB_REF#refs/tags/v}
        export DESC="Release $VERSION"
        node scripts/deploy-app.js
      env:
        ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
        ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        APPLE_ID: ${{ secrets.APPLE_ID }}
        APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
        DISTRIBUTION_TOKEN: ${{ secrets.DISTRIBUTION_TOKEN }}

3.2 GitLab CI

# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  NODE_VERSION: "16"
  npm_config_cache: "$CI_PROJECT_DIR/.npm"
  CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/Cypress"

cache:
  paths:
    - .npm/
    - cache/Cypress/
    - node_modules/

before_script:
  - node --version
  - npm --version
  - npm ci --cache .npm --prefer-offline

test:
  stage: test
  image: node:$NODE_VERSION
  script:
    - npm run lint
    - npm run test
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

build:h5:
  stage: build
  image: node:$NODE_VERSION
  script:
    - npm run build:h5
  artifacts:
    paths:
      - dist/build/h5/
    expire_in: 1 week
  only:
    - main
    - develop
    - tags

build:mp-weixin:
  stage: build
  image: node:$NODE_VERSION
  script:
    - npm run build:mp-weixin
  artifacts:
    paths:
      - dist/build/mp-weixin/
    expire_in: 1 week
  only:
    - tags

build:app-plus:
  stage: build
  image: node:$NODE_VERSION
  script:
    - npm run build:app-plus
  artifacts:
    paths:
      - dist/build/app-plus/
    expire_in: 1 week
  only:
    - tags

deploy:h5:staging:
  stage: deploy
  image: node:$NODE_VERSION
  script:
    - npm run deploy:staging
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop
  dependencies:
    - build:h5

deploy:h5:production:
  stage: deploy
  image: node:$NODE_VERSION
  script:
    - npm run deploy:production
  environment:
    name: production
    url: https://app.example.com
  only:
    - main
  dependencies:
    - build:h5
  when: manual

deploy:miniprogram:
  stage: deploy
  image: node:$NODE_VERSION
  script:
    - export VERSION=${CI_COMMIT_TAG#v}
    - export DESC="Release $VERSION"
    - node scripts/deploy-miniprogram.js
  only:
    - tags
  dependencies:
    - build:mp-weixin

deploy:app:
  stage: deploy
  image: node:$NODE_VERSION
  script:
    - export VERSION=${CI_COMMIT_TAG#v}
    - export DESC="Release $VERSION"
    - node scripts/deploy-app.js
  only:
    - tags
  dependencies:
    - build:app-plus
  when: manual

4. 版本管理

4.1 语义化版本

// scripts/version-manager.js
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')

class VersionManager {
  constructor() {
    this.packagePath = path.resolve(__dirname, '../package.json')
    this.manifestPath = path.resolve(__dirname, '../src/manifest.json')
  }
  
  // 获取当前版本
  getCurrentVersion() {
    const packageJson = JSON.parse(fs.readFileSync(this.packagePath, 'utf8'))
    return packageJson.version
  }
  
  // 更新版本
  updateVersion(type = 'patch') {
    const currentVersion = this.getCurrentVersion()
    const newVersion = this.incrementVersion(currentVersion, type)
    
    console.log(`版本更新: ${currentVersion} -> ${newVersion}`)
    
    // 更新package.json
    this.updatePackageVersion(newVersion)
    
    // 更新manifest.json
    this.updateManifestVersion(newVersion)
    
    // 创建git tag
    this.createGitTag(newVersion)
    
    return newVersion
  }
  
  // 版本号递增
  incrementVersion(version, type) {
    const parts = version.split('.').map(Number)
    
    switch (type) {
      case 'major':
        parts[0]++
        parts[1] = 0
        parts[2] = 0
        break
      case 'minor':
        parts[1]++
        parts[2] = 0
        break
      case 'patch':
      default:
        parts[2]++
        break
    }
    
    return parts.join('.')
  }
  
  // 更新package.json版本
  updatePackageVersion(version) {
    const packageJson = JSON.parse(fs.readFileSync(this.packagePath, 'utf8'))
    packageJson.version = version
    fs.writeFileSync(this.packagePath, JSON.stringify(packageJson, null, 2))
  }
  
  // 更新manifest.json版本
  updateManifestVersion(version) {
    const manifest = JSON.parse(fs.readFileSync(this.manifestPath, 'utf8'))
    manifest.versionName = version
    manifest.versionCode = this.getVersionCode(version)
    fs.writeFileSync(this.manifestPath, JSON.stringify(manifest, null, 2))
  }
  
  // 生成版本代码
  getVersionCode(version) {
    const parts = version.split('.').map(Number)
    return parts[0] * 10000 + parts[1] * 100 + parts[2]
  }
  
  // 创建Git标签
  createGitTag(version) {
    try {
      execSync(`git add .`)
      execSync(`git commit -m "chore: release v${version}"`)
      execSync(`git tag v${version}`)
      console.log(`Git标签创建成功: v${version}`)
    } catch (error) {
      console.error('Git标签创建失败:', error.message)
    }
  }
  
  // 生成变更日志
  generateChangelog(version) {
    const changelogPath = path.resolve(__dirname, '../CHANGELOG.md')
    const date = new Date().toISOString().split('T')[0]
    
    let changelog = ''
    if (fs.existsSync(changelogPath)) {
      changelog = fs.readFileSync(changelogPath, 'utf8')
    }
    
    const newEntry = `## [${version}] - ${date}\n\n### Added\n- 新功能\n\n### Changed\n- 功能改进\n\n### Fixed\n- 问题修复\n\n`
    
    const updatedChangelog = newEntry + changelog
    fs.writeFileSync(changelogPath, updatedChangelog)
    
    console.log('变更日志已更新')
  }
}

// 使用示例
const versionManager = new VersionManager()
const type = process.argv[2] || 'patch'
const newVersion = versionManager.updateVersion(type)
versionManager.generateChangelog(newVersion)

module.exports = VersionManager

4.2 发布检查清单

// scripts/pre-release-check.js
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')

class PreReleaseChecker {
  constructor() {
    this.checks = []
    this.results = []
  }
  
  // 添加检查项
  addCheck(name, checkFn) {
    this.checks.push({ name, checkFn })
  }
  
  // 运行所有检查
  async runChecks() {
    console.log('开始发布前检查...')
    
    for (const check of this.checks) {
      try {
        console.log(`检查: ${check.name}`)
        const result = await check.checkFn()
        this.results.push({ name: check.name, status: 'pass', result })
        console.log(`✓ ${check.name} 通过`)
      } catch (error) {
        this.results.push({ name: check.name, status: 'fail', error: error.message })
        console.log(`✗ ${check.name} 失败: ${error.message}`)
      }
    }
    
    this.generateReport()
  }
  
  // 生成检查报告
  generateReport() {
    const passCount = this.results.filter(r => r.status === 'pass').length
    const failCount = this.results.filter(r => r.status === 'fail').length
    
    console.log('\n=== 发布前检查报告 ===')
    console.log(`总检查项: ${this.results.length}`)
    console.log(`通过: ${passCount}`)
    console.log(`失败: ${failCount}`)
    
    if (failCount > 0) {
      console.log('\n失败项目:')
      this.results
        .filter(r => r.status === 'fail')
        .forEach(r => console.log(`- ${r.name}: ${r.error}`))
      
      process.exit(1)
    } else {
      console.log('\n所有检查通过,可以发布!')
    }
  }
}

// 预定义检查项
const checker = new PreReleaseChecker()

// 代码质量检查
checker.addCheck('ESLint检查', () => {
  execSync('npm run lint', { stdio: 'pipe' })
  return '代码格式正确'
})

// 单元测试
checker.addCheck('单元测试', () => {
  execSync('npm run test', { stdio: 'pipe' })
  return '所有测试通过'
})

// 构建测试
checker.addCheck('构建测试', () => {
  execSync('npm run build:h5', { stdio: 'pipe' })
  return '构建成功'
})

// 配置文件检查
checker.addCheck('配置文件检查', () => {
  const manifestPath = path.resolve(__dirname, '../src/manifest.json')
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
  
  if (!manifest.appid) {
    throw new Error('缺少appid配置')
  }
  
  if (!manifest.versionName) {
    throw new Error('缺少版本号配置')
  }
  
  return '配置文件正确'
})

// 依赖检查
checker.addCheck('依赖安全检查', () => {
  try {
    execSync('npm audit --audit-level=high', { stdio: 'pipe' })
    return '依赖安全'
  } catch (error) {
    throw new Error('发现高危依赖漏洞')
  }
})

// 文件大小检查
checker.addCheck('文件大小检查', () => {
  const distPath = path.resolve(__dirname, '../dist/build/h5')
  if (!fs.existsSync(distPath)) {
    throw new Error('构建文件不存在')
  }
  
  const stats = fs.statSync(distPath)
  const sizeInMB = stats.size / (1024 * 1024)
  
  if (sizeInMB > 50) {
    throw new Error(`构建文件过大: ${sizeInMB.toFixed(2)}MB`)
  }
  
  return `文件大小正常: ${sizeInMB.toFixed(2)}MB`
})

// 运行检查
if (require.main === module) {
  checker.runChecks().catch(console.error)
}

module.exports = PreReleaseChecker

4.3 自动化发布脚本

// scripts/release.js
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const VersionManager = require('./version-manager')
const PreReleaseChecker = require('./pre-release-check')

class ReleaseManager {
  constructor(options = {}) {
    this.options = {
      skipTests: false,
      skipBuild: false,
      platforms: ['h5', 'mp-weixin'],
      ...options
    }
    
    this.versionManager = new VersionManager()
    this.checker = new PreReleaseChecker()
  }
  
  // 执行发布流程
  async release(versionType = 'patch') {
    try {
      console.log('🚀 开始发布流程...')
      
      // 1. 发布前检查
      if (!this.options.skipTests) {
        console.log('\n📋 执行发布前检查...')
        await this.checker.runChecks()
      }
      
      // 2. 更新版本号
      console.log('\n📝 更新版本号...')
      const newVersion = this.versionManager.updateVersion(versionType)
      
      // 3. 构建项目
      if (!this.options.skipBuild) {
        console.log('\n🔨 构建项目...')
        await this.buildProjects()
      }
      
      // 4. 提交代码
      console.log('\n📤 提交代码...')
      this.commitChanges(newVersion)
      
      // 5. 创建标签
      console.log('\n🏷️ 创建Git标签...')
      this.createTag(newVersion)
      
      // 6. 推送到远程
      console.log('\n⬆️ 推送到远程仓库...')
      this.pushToRemote()
      
      // 7. 发布通知
      console.log('\n📢 发送发布通知...')
      await this.sendNotification(newVersion)
      
      console.log(`\n✅ 发布完成!版本: v${newVersion}`)
      
    } catch (error) {
      console.error('\n❌ 发布失败:', error.message)
      process.exit(1)
    }
  }
  
  // 构建项目
  async buildProjects() {
    for (const platform of this.options.platforms) {
      console.log(`构建 ${platform}...`)
      execSync(`npm run build:${platform}`, { stdio: 'inherit' })
    }
  }
  
  // 提交更改
  commitChanges(version) {
    execSync('git add .')
    execSync(`git commit -m "chore: release v${version}"`)
  }
  
  // 创建标签
  createTag(version) {
    execSync(`git tag v${version}`)
  }
  
  // 推送到远程
  pushToRemote() {
    execSync('git push origin main')
    execSync('git push origin --tags')
  }
  
  // 发送通知
  async sendNotification(version) {
    const webhookUrl = process.env.RELEASE_WEBHOOK_URL
    if (!webhookUrl) {
      console.log('未配置通知webhook,跳过通知')
      return
    }
    
    try {
      const axios = require('axios')
      await axios.post(webhookUrl, {
        text: `🎉 新版本发布: v${version}`,
        version,
        timestamp: new Date().toISOString()
      })
      console.log('发布通知发送成功')
    } catch (error) {
      console.warn('发布通知发送失败:', error.message)
    }
  }
}

// 命令行接口
if (require.main === module) {
  const args = process.argv.slice(2)
  const versionType = args[0] || 'patch'
  const options = {}
  
  // 解析命令行参数
  if (args.includes('--skip-tests')) {
    options.skipTests = true
  }
  
  if (args.includes('--skip-build')) {
    options.skipBuild = true
  }
  
  const platformIndex = args.indexOf('--platforms')
  if (platformIndex !== -1 && args[platformIndex + 1]) {
    options.platforms = args[platformIndex + 1].split(',')
  }
  
  const releaseManager = new ReleaseManager(options)
  releaseManager.release(versionType).catch(console.error)
}

module.exports = ReleaseManager

5. 监控与维护

5.1 应用监控

// utils/monitor.js
class AppMonitor {
  constructor(config) {
    this.config = config
    this.errorQueue = []
    this.performanceQueue = []
    this.userActionQueue = []
  }
  
  // 错误监控
  captureError(error, context = {}) {
    const errorInfo = {
      message: error.message,
      stack: error.stack,
      timestamp: Date.now(),
      url: this.getCurrentPage(),
      userAgent: this.getUserAgent(),
      userId: this.getUserId(),
      ...context
    }
    
    this.errorQueue.push(errorInfo)
    
    // 立即上报严重错误
    if (this.isCriticalError(error)) {
      this.reportError(errorInfo)
    } else {
      // 批量上报
      this.scheduleReport('error')
    }
  }
  
  // 性能监控
  capturePerformance(metric) {
    const performanceInfo = {
      ...metric,
      timestamp: Date.now(),
      page: this.getCurrentPage(),
      userId: this.getUserId()
    }
    
    this.performanceQueue.push(performanceInfo)
    this.scheduleReport('performance')
  }
  
  // 用户行为监控
  captureUserAction(action, data = {}) {
    const actionInfo = {
      action,
      data,
      timestamp: Date.now(),
      page: this.getCurrentPage(),
      userId: this.getUserId()
    }
    
    this.userActionQueue.push(actionInfo)
    this.scheduleReport('userAction')
  }
  
  // 判断是否为严重错误
  isCriticalError(error) {
    const criticalKeywords = ['network', 'timeout', 'crash', 'memory']
    return criticalKeywords.some(keyword => 
      error.message.toLowerCase().includes(keyword)
    )
  }
  
  // 获取当前页面
  getCurrentPage() {
    // #ifdef H5
    return window.location.pathname
    // #endif
    
    // #ifdef APP-PLUS || MP
    const pages = getCurrentPages()
    return pages.length > 0 ? pages[pages.length - 1].route : 'unknown'
    // #endif
  }
  
  // 获取用户代理
  getUserAgent() {
    // #ifdef H5
    return navigator.userAgent
    // #endif
    
    // #ifdef APP-PLUS
    return plus.navigator.getUserAgent()
    // #endif
    
    // #ifdef MP
    const systemInfo = uni.getSystemInfoSync()
    return `${systemInfo.platform} ${systemInfo.system}`
    // #endif
  }
  
  // 获取用户ID
  getUserId() {
    return uni.getStorageSync('userId') || 'anonymous'
  }
  
  // 调度上报
  scheduleReport(type) {
    if (this.reportTimer) {
      clearTimeout(this.reportTimer)
    }
    
    this.reportTimer = setTimeout(() => {
      this.batchReport()
    }, this.config.reportInterval || 5000)
  }
  
  // 批量上报
  async batchReport() {
    try {
      const data = {
        errors: this.errorQueue.splice(0),
        performance: this.performanceQueue.splice(0),
        userActions: this.userActionQueue.splice(0),
        timestamp: Date.now(),
        appVersion: this.config.appVersion,
        platform: this.getPlatform()
      }
      
      if (data.errors.length === 0 && 
          data.performance.length === 0 && 
          data.userActions.length === 0) {
        return
      }
      
      await this.sendReport(data)
    } catch (error) {
      console.error('监控数据上报失败:', error)
    }
  }
  
  // 发送报告
  async sendReport(data) {
    return new Promise((resolve, reject) => {
      uni.request({
        url: this.config.reportUrl,
        method: 'POST',
        data,
        header: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.config.apiKey}`
        },
        success: (res) => {
          if (res.statusCode === 200) {
            resolve(res.data)
          } else {
            reject(new Error(`上报失败: ${res.statusCode}`))
          }
        },
        fail: reject
      })
    })
  }
  
  // 获取平台信息
  getPlatform() {
    // #ifdef H5
    return 'h5'
    // #endif
    
    // #ifdef APP-PLUS
    return 'app'
    // #endif
    
    // #ifdef MP-WEIXIN
    return 'mp-weixin'
    // #endif
    
    // #ifdef MP-ALIPAY
    return 'mp-alipay'
    // #endif
    
    return 'unknown'
  }
}

// 全局监控实例
const monitor = new AppMonitor({
  reportUrl: 'https://api.example.com/monitor',
  apiKey: 'your-api-key',
  appVersion: '1.0.0',
  reportInterval: 5000
})

// 全局错误处理
uni.onError((error) => {
  monitor.captureError(error)
})

// 全局未处理的Promise拒绝
uni.onUnhandledRejection((event) => {
  monitor.captureError(new Error(event.reason), {
    type: 'unhandledRejection'
  })
})

export default monitor

5.2 性能监控

// utils/performance.js
import monitor from './monitor'

class PerformanceMonitor {
  constructor() {
    this.pageStartTime = 0
    this.navigationStartTime = 0
  }
  
  // 页面加载性能监控
  measurePageLoad() {
    // #ifdef H5
    if (window.performance && window.performance.timing) {
      const timing = window.performance.timing
      const metrics = {
        // DNS查询时间
        dnsTime: timing.domainLookupEnd - timing.domainLookupStart,
        // TCP连接时间
        tcpTime: timing.connectEnd - timing.connectStart,
        // 请求时间
        requestTime: timing.responseEnd - timing.requestStart,
        // 解析DOM树时间
        domParseTime: timing.domComplete - timing.domLoading,
        // 白屏时间
        whiteScreenTime: timing.responseStart - timing.navigationStart,
        // 首屏时间
        firstScreenTime: timing.loadEventEnd - timing.navigationStart
      }
      
      monitor.capturePerformance({
        type: 'pageLoad',
        metrics
      })
    }
    // #endif
    
    // #ifdef APP-PLUS || MP
    const endTime = Date.now()
    const loadTime = endTime - this.pageStartTime
    
    monitor.capturePerformance({
      type: 'pageLoad',
      metrics: {
        loadTime
      }
    })
    // #endif
  }
  
  // API请求性能监控
  measureApiRequest(url, startTime, endTime, success) {
    const duration = endTime - startTime
    
    monitor.capturePerformance({
      type: 'apiRequest',
      metrics: {
        url,
        duration,
        success,
        timestamp: startTime
      }
    })
  }
  
  // 内存使用监控
  measureMemoryUsage() {
    // #ifdef H5
    if (window.performance && window.performance.memory) {
      const memory = window.performance.memory
      monitor.capturePerformance({
        type: 'memory',
        metrics: {
          usedJSHeapSize: memory.usedJSHeapSize,
          totalJSHeapSize: memory.totalJSHeapSize,
          jsHeapSizeLimit: memory.jsHeapSizeLimit
        }
      })
    }
    // #endif
    
    // #ifdef APP-PLUS
    plus.runtime.getProperty(plus.runtime.appid, (info) => {
      monitor.capturePerformance({
        type: 'memory',
        metrics: {
          memory: info.memory
        }
      })
    })
    // #endif
  }
  
  // 帧率监控
  measureFPS() {
    let lastTime = Date.now()
    let frameCount = 0
    
    const measureFrame = () => {
      frameCount++
      const currentTime = Date.now()
      
      if (currentTime - lastTime >= 1000) {
        const fps = Math.round((frameCount * 1000) / (currentTime - lastTime))
        
        monitor.capturePerformance({
          type: 'fps',
          metrics: {
            fps,
            timestamp: currentTime
          }
        })
        
        frameCount = 0
        lastTime = currentTime
      }
      
      requestAnimationFrame(measureFrame)
    }
    
    requestAnimationFrame(measureFrame)
  }
  
  // 开始页面计时
  startPageTiming() {
    this.pageStartTime = Date.now()
  }
  
  // 结束页面计时
  endPageTiming() {
    this.measurePageLoad()
  }
}

const performanceMonitor = new PerformanceMonitor()

// 页面生命周期钩子
export const pageLifecycleMixin = {
  onLoad() {
    performanceMonitor.startPageTiming()
  },
  
  onReady() {
    performanceMonitor.endPageTiming()
  },
  
  onShow() {
    // 开始FPS监控
    performanceMonitor.measureFPS()
    
    // 定期监控内存使用
    this.memoryTimer = setInterval(() => {
      performanceMonitor.measureMemoryUsage()
    }, 30000)
  },
  
  onHide() {
    // 停止内存监控
    if (this.memoryTimer) {
      clearInterval(this.memoryTimer)
    }
  }
}

export default performanceMonitor

5.3 日志管理

// utils/logger.js
class Logger {
  constructor(config = {}) {
    this.config = {
      level: 'info',
      maxLogs: 1000,
      enableConsole: true,
      enableRemote: true,
      remoteUrl: '',
      ...config
    }
    
    this.logs = []
    this.levels = {
      debug: 0,
      info: 1,
      warn: 2,
      error: 3
    }
  }
  
  // 记录日志
  log(level, message, data = {}) {
    if (this.levels[level] < this.levels[this.config.level]) {
      return
    }
    
    const logEntry = {
      level,
      message,
      data,
      timestamp: new Date().toISOString(),
      page: this.getCurrentPage(),
      userId: this.getUserId()
    }
    
    // 添加到本地日志队列
    this.logs.push(logEntry)
    
    // 限制日志数量
    if (this.logs.length > this.config.maxLogs) {
      this.logs.shift()
    }
    
    // 控制台输出
    if (this.config.enableConsole) {
      this.consoleLog(level, message, data)
    }
    
    // 远程上报
    if (this.config.enableRemote && level === 'error') {
      this.reportLog(logEntry)
    }
  }
  
  // 各级别日志方法
  debug(message, data) {
    this.log('debug', message, data)
  }
  
  info(message, data) {
    this.log('info', message, data)
  }
  
  warn(message, data) {
    this.log('warn', message, data)
  }
  
  error(message, data) {
    this.log('error', message, data)
  }
  
  // 控制台输出
  consoleLog(level, message, data) {
    const timestamp = new Date().toLocaleTimeString()
    const prefix = `[${timestamp}] [${level.toUpperCase()}]`
    
    switch (level) {
      case 'debug':
        console.debug(prefix, message, data)
        break
      case 'info':
        console.info(prefix, message, data)
        break
      case 'warn':
        console.warn(prefix, message, data)
        break
      case 'error':
        console.error(prefix, message, data)
        break
    }
  }
  
  // 上报日志
  async reportLog(logEntry) {
    if (!this.config.remoteUrl) {
      return
    }
    
    try {
      await uni.request({
        url: this.config.remoteUrl,
        method: 'POST',
        data: logEntry,
        header: {
          'Content-Type': 'application/json'
        }
      })
    } catch (error) {
      console.error('日志上报失败:', error)
    }
  }
  
  // 获取所有日志
  getLogs(level = null) {
    if (level) {
      return this.logs.filter(log => log.level === level)
    }
    return this.logs
  }
  
  // 清空日志
  clearLogs() {
    this.logs = []
  }
  
  // 导出日志
  exportLogs() {
    const logsText = this.logs.map(log => {
      return `${log.timestamp} [${log.level.toUpperCase()}] ${log.message} ${JSON.stringify(log.data)}`
    }).join('\n')
    
    return logsText
  }
  
  // 获取当前页面
  getCurrentPage() {
    const pages = getCurrentPages()
    return pages.length > 0 ? pages[pages.length - 1].route : 'unknown'
  }
  
  // 获取用户ID
  getUserId() {
    return uni.getStorageSync('userId') || 'anonymous'
  }
}

// 创建全局日志实例
const logger = new Logger({
  level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',
  remoteUrl: 'https://api.example.com/logs'
})

export default logger

总结

本章详细介绍了 UniApp 应用的打包发布与部署流程,包括:

  1. 配置管理:manifest.json 配置、环境配置、构建脚本
  2. 多平台发布:H5、小程序、原生 App 的发布流程
  3. CI/CD 集成:自动化构建和部署
  4. 版本管理:语义化版本控制和发布流程
  5. 监控维护:应用监控、性能监控、日志管理

掌握这些内容,可以建立完整的 UniApp 应用发布流程,确保应用质量和发布效率。通过自动化工具和监控系统,能够实现高效的开发运维一体化流程。