发布部署
小程序的发布部署是开发流程的最后一环,涉及代码审核、版本管理、灰度发布等多个环节。本章将详细介绍小程序发布部署的完整流程和最佳实践。
发布前准备
代码质量检查
1. 代码审查清单
javascript
// 代码质量检查工具
class CodeQualityChecker {
constructor() {
this.issues = []
}
checkCodeQuality(projectPath) {
console.log('开始代码质量检查...')
this.checkFileStructure(projectPath)
this.checkCodeStyle(projectPath)
this.checkPerformance(projectPath)
this.checkSecurity(projectPath)
this.checkCompatibility(projectPath)
return this.generateReport()
}
checkFileStructure(projectPath) {
const requiredFiles = [
'app.js',
'app.json',
'app.wxss',
'project.config.json'
]
requiredFiles.forEach(file => {
// 检查必需文件是否存在
if (!this.fileExists(`${projectPath}/${file}`)) {
this.addIssue('structure', `缺少必需文件: ${file}`, 'high')
}
})
// 检查页面配置
this.checkPageConfiguration(projectPath)
}
checkPageConfiguration(projectPath) {
// 检查 app.json 中的页面配置
const appConfig = this.readJsonFile(`${projectPath}/app.json`)
if (!appConfig.pages || appConfig.pages.length === 0) {
this.addIssue('config', 'app.json 中未配置页面', 'high')
}
// 检查每个页面文件是否存在
appConfig.pages?.forEach(pagePath => {
const pageFiles = [
`${pagePath}.js`,
`${pagePath}.wxml`,
`${pagePath}.wxss`,
`${pagePath}.json`
]
pageFiles.forEach(file => {
if (!this.fileExists(`${projectPath}/${file}`)) {
this.addIssue('structure', `页面文件缺失: ${file}`, 'medium')
}
})
})
}
checkCodeStyle(projectPath) {
// 检查代码风格
const jsFiles = this.findFiles(projectPath, '.js')
jsFiles.forEach(file => {
const content = this.readFile(file)
// 检查是否使用了 console.log(生产环境应该移除)
if (content.includes('console.log')) {
this.addIssue('style', `${file} 包含 console.log 语句`, 'low')
}
// 检查是否有未使用的变量
this.checkUnusedVariables(file, content)
// 检查函数复杂度
this.checkFunctionComplexity(file, content)
})
}
checkPerformance(projectPath) {
// 检查性能相关问题
const imageFiles = this.findFiles(projectPath, ['.jpg', '.png', '.gif'])
imageFiles.forEach(file => {
const fileSize = this.getFileSize(file)
// 检查图片大小
if (fileSize > 500 * 1024) { // 500KB
this.addIssue('performance', `图片文件过大: ${file} (${Math.round(fileSize / 1024)}KB)`, 'medium')
}
})
// 检查代码包大小
const packageSize = this.calculatePackageSize(projectPath)
if (packageSize > 2 * 1024 * 1024) { // 2MB
this.addIssue('performance', `代码包过大: ${Math.round(packageSize / 1024 / 1024)}MB`, 'high')
}
}
checkSecurity(projectPath) {
// 检查安全相关问题
const jsFiles = this.findFiles(projectPath, '.js')
jsFiles.forEach(file => {
const content = this.readFile(file)
// 检查硬编码的敏感信息
const sensitivePatterns = [
/password\s*[:=]\s*['"][^'"]+['"]/i,
/token\s*[:=]\s*['"][^'"]+['"]/i,
/secret\s*[:=]\s*['"][^'"]+['"]/i,
/key\s*[:=]\s*['"][^'"]+['"]/i
]
sensitivePatterns.forEach(pattern => {
if (pattern.test(content)) {
this.addIssue('security', `${file} 可能包含硬编码的敏感信息`, 'high')
}
})
})
}
checkCompatibility(projectPath) {
// 检查兼容性问题
const projectConfig = this.readJsonFile(`${projectPath}/project.config.json`)
if (projectConfig.libVersion) {
const version = projectConfig.libVersion
const minVersion = '2.10.0'
if (this.compareVersion(version, minVersion) < 0) {
this.addIssue('compatibility', `基础库版本过低: ${version},建议升级到 ${minVersion} 以上`, 'medium')
}
}
}
addIssue(category, message, severity) {
this.issues.push({
category: category,
message: message,
severity: severity,
timestamp: new Date().toISOString()
})
}
generateReport() {
const report = {
timestamp: new Date().toISOString(),
totalIssues: this.issues.length,
highSeverity: this.issues.filter(i => i.severity === 'high').length,
mediumSeverity: this.issues.filter(i => i.severity === 'medium').length,
lowSeverity: this.issues.filter(i => i.severity === 'low').length,
issues: this.issues,
passed: this.issues.filter(i => i.severity === 'high').length === 0
}
console.log('代码质量检查报告:', report)
return report
}
// 辅助方法
fileExists(path) {
// 实际实现中需要检查文件是否存在
return true
}
readFile(path) {
// 实际实现中需要读取文件内容
return ''
}
readJsonFile(path) {
// 实际实现中需要读取并解析 JSON 文件
return {}
}
findFiles(path, extensions) {
// 实际实现中需要查找指定扩展名的文件
return []
}
getFileSize(path) {
// 实际实现中需要获取文件大小
return 0
}
calculatePackageSize(path) {
// 实际实现中需要计算代码包大小
return 0
}
compareVersion(v1, v2) {
// 版本号比较
const arr1 = v1.split('.')
const arr2 = v2.split('.')
for (let i = 0; i < Math.max(arr1.length, arr2.length); i++) {
const num1 = parseInt(arr1[i] || '0')
const num2 = parseInt(arr2[i] || '0')
if (num1 > num2) return 1
if (num1 < num2) return -1
}
return 0
}
}
// 使用示例
const checker = new CodeQualityChecker()
const report = checker.checkCodeQuality('./miniprogram')
if (!report.passed) {
console.error('代码质量检查未通过,请修复以下问题:')
report.issues.forEach(issue => {
console.log(`[${issue.severity.toUpperCase()}] ${issue.message}`)
})
}
2. 性能测试
javascript
// 性能测试工具
class PerformanceTester {
constructor() {
this.testResults = []
}
async runPerformanceTests() {
console.log('开始性能测试...')
await this.testStartupTime()
await this.testPageLoadTime()
await this.testMemoryUsage()
await this.testNetworkPerformance()
return this.generateReport()
}
async testStartupTime() {
console.log('测试启动时间...')
const iterations = 5
const startupTimes = []
for (let i = 0; i < iterations; i++) {
const startTime = Date.now()
// 模拟小程序启动
await this.simulateStartup()
const endTime = Date.now()
startupTimes.push(endTime - startTime)
}
const avgStartupTime = startupTimes.reduce((a, b) => a + b, 0) / startupTimes.length
this.testResults.push({
test: 'startup_time',
avgTime: avgStartupTime,
times: startupTimes,
passed: avgStartupTime < 3000 // 3秒内启动
})
}
async testPageLoadTime() {
console.log('测试页面加载时间...')
const pages = ['pages/index/index', 'pages/profile/profile', 'pages/settings/settings']
for (const page of pages) {
const loadTimes = []
for (let i = 0; i < 3; i++) {
const startTime = Date.now()
await this.simulatePageLoad(page)
const endTime = Date.now()
loadTimes.push(endTime - startTime)
}
const avgLoadTime = loadTimes.reduce((a, b) => a + b, 0) / loadTimes.length
this.testResults.push({
test: 'page_load_time',
page: page,
avgTime: avgLoadTime,
times: loadTimes,
passed: avgLoadTime < 2000 // 2秒内加载
})
}
}
async testMemoryUsage() {
console.log('测试内存使用...')
// 模拟内存使用测试
const memoryUsage = await this.simulateMemoryTest()
this.testResults.push({
test: 'memory_usage',
usage: memoryUsage,
passed: memoryUsage < 50 * 1024 * 1024 // 50MB 以内
})
}
async testNetworkPerformance() {
console.log('测试网络性能...')
const apis = [
'/api/user/info',
'/api/data/list',
'/api/config/settings'
]
for (const api of apis) {
const responseTimes = []
for (let i = 0; i < 5; i++) {
const startTime = Date.now()
await this.simulateApiCall(api)
const endTime = Date.now()
responseTimes.push(endTime - startTime)
}
const avgResponseTime = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length
this.testResults.push({
test: 'api_response_time',
api: api,
avgTime: avgResponseTime,
times: responseTimes,
passed: avgResponseTime < 1000 // 1秒内响应
})
}
}
generateReport() {
const passedTests = this.testResults.filter(r => r.passed).length
const totalTests = this.testResults.length
const report = {
timestamp: new Date().toISOString(),
totalTests: totalTests,
passedTests: passedTests,
failedTests: totalTests - passedTests,
passRate: (passedTests / totalTests * 100).toFixed(2) + '%',
results: this.testResults,
passed: passedTests === totalTests
}
console.log('性能测试报告:', report)
return report
}
// 模拟方法
async simulateStartup() {
return new Promise(resolve => {
setTimeout(resolve, Math.random() * 1000 + 500)
})
}
async simulatePageLoad(page) {
return new Promise(resolve => {
setTimeout(resolve, Math.random() * 500 + 200)
})
}
async simulateMemoryTest() {
return Math.random() * 30 * 1024 * 1024 + 20 * 1024 * 1024
}
async simulateApiCall(api) {
return new Promise(resolve => {
setTimeout(resolve, Math.random() * 300 + 100)
})
}
}
配置文件检查
1. 项目配置验证
javascript
// project.config.json 验证
const validateProjectConfig = (config) => {
const issues = []
// 检查必需字段
const requiredFields = ['appid', 'projectname', 'libVersion']
requiredFields.forEach(field => {
if (!config[field]) {
issues.push(`缺少必需字段: ${field}`)
}
})
// 检查 appid 格式
if (config.appid && !/^wx[a-f0-9]{16}$/.test(config.appid)) {
issues.push('appid 格式不正确')
}
// 检查基础库版本
if (config.libVersion) {
const version = config.libVersion
const minVersion = '2.10.0'
if (compareVersion(version, minVersion) < 0) {
issues.push(`基础库版本过低: ${version},建议升级到 ${minVersion} 以上`)
}
}
// 检查编译设置
if (config.setting) {
const setting = config.setting
// 生产环境建议设置
const productionSettings = {
es6: true,
postcss: true,
minified: true,
urlCheck: true
}
Object.keys(productionSettings).forEach(key => {
if (setting[key] !== productionSettings[key]) {
issues.push(`建议设置 setting.${key} 为 ${productionSettings[key]}`)
}
})
}
return {
valid: issues.length === 0,
issues: issues
}
}
// app.json 验证
const validateAppConfig = (config) => {
const issues = []
// 检查页面配置
if (!config.pages || config.pages.length === 0) {
issues.push('未配置页面')
}
// 检查窗口配置
if (config.window) {
const window = config.window
// 检查导航栏配置
if (!window.navigationBarTitleText) {
issues.push('建议设置导航栏标题')
}
if (window.navigationBarBackgroundColor &&
!/^#[0-9a-fA-F]{6}$/.test(window.navigationBarBackgroundColor)) {
issues.push('导航栏背景色格式不正确')
}
}
// 检查 tabBar 配置
if (config.tabBar) {
const tabBar = config.tabBar
if (!tabBar.list || tabBar.list.length < 2 || tabBar.list.length > 5) {
issues.push('tabBar 列表数量应该在 2-5 个之间')
}
tabBar.list?.forEach((item, index) => {
if (!item.pagePath) {
issues.push(`tabBar 第 ${index + 1} 项缺少 pagePath`)
}
if (!item.text) {
issues.push(`tabBar 第 ${index + 1} 项缺少 text`)
}
})
}
// 检查权限配置
if (config.permission) {
Object.keys(config.permission).forEach(key => {
if (!config.permission[key].desc) {
issues.push(`权限 ${key} 缺少描述`)
}
})
}
return {
valid: issues.length === 0,
issues: issues
}
}
版本管理
版本号规范
javascript
// 版本管理工具
class VersionManager {
constructor() {
this.currentVersion = this.getCurrentVersion()
}
getCurrentVersion() {
// 从 package.json 或其他配置文件读取当前版本
return '1.0.0'
}
// 语义化版本控制
bumpVersion(type = 'patch') {
const [major, minor, patch] = this.currentVersion.split('.').map(Number)
let newVersion
switch (type) {
case 'major':
newVersion = `${major + 1}.0.0`
break
case 'minor':
newVersion = `${major}.${minor + 1}.0`
break
case 'patch':
default:
newVersion = `${major}.${minor}.${patch + 1}`
break
}
this.currentVersion = newVersion
this.updateVersionFiles(newVersion)
return newVersion
}
updateVersionFiles(version) {
// 更新相关文件中的版本号
console.log(`更新版本号到 ${version}`)
// 更新 package.json
this.updatePackageJson(version)
// 更新 app.json 中的版本信息(如果有)
this.updateAppJson(version)
// 创建版本标签
this.createVersionTag(version)
}
updatePackageJson(version) {
// 实际实现中需要读取和更新 package.json
console.log(`更新 package.json 版本号: ${version}`)
}
updateAppJson(version) {
// 实际实现中需要更新 app.json
console.log(`更新 app.json 版本信息: ${version}`)
}
createVersionTag(version) {
// 创建 Git 标签
console.log(`创建版本标签: v${version}`)
// 实际实现: git tag v${version}
}
// 生成版本发布说明
generateReleaseNotes(version, changes) {
const releaseNotes = {
version: version,
date: new Date().toISOString().split('T')[0],
changes: changes,
features: changes.filter(c => c.type === 'feature'),
bugfixes: changes.filter(c => c.type === 'bugfix'),
improvements: changes.filter(c => c.type === 'improvement')
}
const markdown = this.formatReleaseNotes(releaseNotes)
console.log('发布说明:', markdown)
return releaseNotes
}
formatReleaseNotes(notes) {
let markdown = `# 版本 ${notes.version} (${notes.date})\n\n`
if (notes.features.length > 0) {
markdown += '## 新功能\n'
notes.features.forEach(feature => {
markdown += `- ${feature.description}\n`
})
markdown += '\n'
}
if (notes.improvements.length > 0) {
markdown += '## 改进\n'
notes.improvements.forEach(improvement => {
markdown += `- ${improvement.description}\n`
})
markdown += '\n'
}
if (notes.bugfixes.length > 0) {
markdown += '## 错误修复\n'
notes.bugfixes.forEach(bugfix => {
markdown += `- ${bugfix.description}\n`
})
markdown += '\n'
}
return markdown
}
}
// 使用示例
const versionManager = new VersionManager()
// 发布补丁版本
const newVersion = versionManager.bumpVersion('patch')
// 生成发布说明
const changes = [
{ type: 'feature', description: '新增用户头像上传功能' },
{ type: 'bugfix', description: '修复页面跳转参数丢失问题' },
{ type: 'improvement', description: '优化列表加载性能' }
]
versionManager.generateReleaseNotes(newVersion, changes)
构建脚本
javascript
// build.js - 构建脚本
const fs = require('fs')
const path = require('path')
class BuildManager {
constructor(config = {}) {
this.config = {
sourceDir: './src',
outputDir: './dist',
environment: 'production',
...config
}
}
async build() {
console.log('开始构建...')
try {
// 清理输出目录
await this.cleanOutputDir()
// 复制源文件
await this.copySourceFiles()
// 处理配置文件
await this.processConfigFiles()
// 压缩代码
await this.minifyCode()
// 优化资源
await this.optimizeAssets()
// 生成构建报告
const report = await this.generateBuildReport()
console.log('构建完成!')
console.log('构建报告:', report)
return report
} catch (error) {
console.error('构建失败:', error)
throw error
}
}
async cleanOutputDir() {
console.log('清理输出目录...')
// 实际实现中需要删除输出目录
}
async copySourceFiles() {
console.log('复制源文件...')
// 实际实现中需要复制源文件到输出目录
}
async processConfigFiles() {
console.log('处理配置文件...')
// 根据环境更新配置
const appConfig = this.readJsonFile('./src/app.json')
if (this.config.environment === 'production') {
// 生产环境配置
appConfig.debug = false
// 移除开发环境的页面
if (appConfig.pages) {
appConfig.pages = appConfig.pages.filter(page =>
!page.includes('debug') && !page.includes('test')
)
}
}
this.writeJsonFile('./dist/app.json', appConfig)
}
async minifyCode() {
console.log('压缩代码...')
// 压缩 JavaScript 文件
const jsFiles = this.findFiles('./dist', '.js')
for (const file of jsFiles) {
await this.minifyJsFile(file)
}
// 压缩 CSS 文件
const cssFiles = this.findFiles('./dist', '.wxss')
for (const file of cssFiles) {
await this.minifyCssFile(file)
}
// 压缩 WXML 文件
const wxmlFiles = this.findFiles('./dist', '.wxml')
for (const file of wxmlFiles) {
await this.minifyWxmlFile(file)
}
}
async optimizeAssets() {
console.log('优化资源...')
// 压缩图片
const imageFiles = this.findFiles('./dist', ['.jpg', '.png', '.gif'])
for (const file of imageFiles) {
await this.compressImage(file)
}
// 转换图片格式
await this.convertImagesToWebP()
}
async generateBuildReport() {
const report = {
timestamp: new Date().toISOString(),
environment: this.config.environment,
sourceDir: this.config.sourceDir,
outputDir: this.config.outputDir,
files: {
total: 0,
js: 0,
wxss: 0,
wxml: 0,
json: 0,
images: 0
},
size: {
total: 0,
beforeOptimization: 0,
afterOptimization: 0,
compressionRatio: 0
}
}
// 统计文件信息
const allFiles = this.findFiles('./dist', '*')
report.files.total = allFiles.length
// 按类型统计
report.files.js = this.findFiles('./dist', '.js').length
report.files.wxss = this.findFiles('./dist', '.wxss').length
report.files.wxml = this.findFiles('./dist', '.wxml').length
report.files.json = this.findFiles('./dist', '.json').length
report.files.images = this.findFiles('./dist', ['.jpg', '.png', '.gif', '.webp']).length
// 计算大小
report.size.total = this.calculateDirectorySize('./dist')
report.size.compressionRatio = ((report.size.beforeOptimization - report.size.afterOptimization) / report.size.beforeOptimization * 100).toFixed(2) + '%'
return report
}
// 辅助方法
readJsonFile(path) {
// 实际实现中需要读取 JSON 文件
return {}
}
writeJsonFile(path, data) {
// 实际实现中需要写入 JSON 文件
}
findFiles(dir, extension) {
// 实际实现中需要查找文件
return []
}
async minifyJsFile(file) {
// 实际实现中需要压缩 JS 文件
}
async minifyCssFile(file) {
// 实际实现中需要压缩 CSS 文件
}
async minifyWxmlFile(file) {
// 实际实现中需要压缩 WXML 文件
}
async compressImage(file) {
// 实际实现中需要压缩图片
}
async convertImagesToWebP() {
// 实际实现中需要转换图片格式
}
calculateDirectorySize(dir) {
// 实际实现中需要计算目录大小
return 0
}
}
// package.json 中的构建脚本
const buildScripts = {
"scripts": {
"build": "node build.js",
"build:dev": "node build.js --env=development",
"build:test": "node build.js --env=testing",
"build:prod": "node build.js --env=production",
"prebuild": "npm run lint && npm run test",
"postbuild": "npm run analyze"
}
}
发布流程
自动化发布
javascript
// deploy.js - 自动化发布脚本
class DeployManager {
constructor(config = {}) {
this.config = {
appId: '',
privateKey: '',
version: '1.0.0',
description: '',
...config
}
}
async deploy() {
console.log('开始自动化发布...')
try {
// 1. 预发布检查
await this.preDeployCheck()
// 2. 构建项目
await this.buildProject()
// 3. 上传代码
await this.uploadCode()
// 4. 提交审核
await this.submitForReview()
// 5. 发布通知
await this.sendNotification()
console.log('发布流程完成!')
} catch (error) {
console.error('发布失败:', error)
await this.handleDeployError(error)
throw error
}
}
async preDeployCheck() {
console.log('执行预发布检查...')
// 检查代码质量
const codeQuality = await this.checkCodeQuality()
if (!codeQuality.passed) {
throw new Error('代码质量检查未通过')
}
// 检查测试覆盖率
const testCoverage = await this.checkTestCoverage()
if (testCoverage < 80) {
console.warn(`测试覆盖率较低: ${testCoverage}%`)
}
// 检查依赖安全性
await this.checkDependencySecurity()
// 检查配置文件
await this.validateConfigurations()
}
async buildProject() {
console.log('构建项目...')
const buildManager = new BuildManager({
environment: 'production'
})
const buildReport = await buildManager.build()
// 检查构建结果
if (buildReport.size.total > 20 * 1024 * 1024) { // 20MB
throw new Error('构建包过大,超过小程序限制')
}
return buildReport
}
async uploadCode() {
console.log('上传代码...')
// 使用微信开发者工具 CLI 上传
const uploadCommand = `cli upload --project ./dist --version ${this.config.version} --desc "${this.config.description}"`
try {
await this.executeCommand(uploadCommand)
console.log('代码上传成功')
} catch (error) {
throw new Error(`代码上传失败: ${error.message}`)
}
}
async submitForReview() {
console.log('提交审核...')
// 调用微信小程序 API 提交审核
const submitData = {
item_list: [
{
address: 'pages/index/index',
tag: '首页',
first_class: '工具',
second_class: '效率',
title: '小程序研究院'
}
]
}
try {
const result = await this.callWechatAPI('/wxa/submit_audit', submitData)
console.log('提交审核成功,审核ID:', result.auditid)
return result.auditid
} catch (error) {
throw new Error(`提交审核失败: ${error.message}`)
}
}
async sendNotification() {
console.log('发送发布通知...')
const notification = {
title: '小程序发布通知',
content: `版本 ${this.config.version} 已提交审核`,
timestamp: new Date().toISOString(),
version: this.config.version,
description: this.config.description
}
// 发送到团队通知渠道(如钉钉、企业微信等)
await this.sendTeamNotification(notification)
// 发送邮件通知
await this.sendEmailNotification(notification)
}
async handleDeployError(error) {
console.log('处理发布错误...')
const errorReport = {
timestamp: new Date().toISOString(),
error: error.message,
stack: error.stack,
version: this.config.version,
stage: this.getCurrentStage()
}
// 发送错误通知
await this.sendErrorNotification(errorReport)
// 回滚操作(如果需要)
await this.rollback()
}
// 辅助方法
async checkCodeQuality() {
const checker = new CodeQualityChecker()
return checker.checkCodeQuality('./src')
}
async checkTestCoverage() {
// 实际实现中需要运行测试并获取覆盖率
return 85
}
async checkDependencySecurity() {
// 检查依赖包安全性
console.log('检查依赖安全性...')
}
async validateConfigurations() {
// 验证配置文件
console.log('验证配置文件...')
}
async executeCommand(command) {
// 执行命令行命令
console.log(`执行命令: ${command}`)
}
async callWechatAPI(endpoint, data) {
// 调用微信 API
console.log(`调用微信 API: ${endpoint}`)
return { auditid: '12345' }
}
async sendTeamNotification(notification) {
// 发送团队通知
console.log('发送团队通知:', notification.title)
}
async sendEmailNotification(notification) {
// 发送邮件通知
console.log('发送邮件通知:', notification.title)
}
async sendErrorNotification(errorReport) {
// 发送错误通知
console.log('发送错误通知:', errorReport.error)
}
async rollback() {
// 回滚操作
console.log('执行回滚操作...')
}
getCurrentStage() {
// 获取当前发布阶段
return 'unknown'
}
}
灰度发布
javascript
// 灰度发布管理
class GrayReleaseManager {
constructor(config = {}) {
this.config = {
grayRatio: 0.1, // 10% 灰度用户
duration: 24 * 60 * 60 * 1000, // 24小时
...config
}
this.grayUsers = new Set()
this.metrics = {
totalUsers: 0,
grayUsers: 0,
errorRate: 0,
crashRate: 0,
performanceScore: 0
}
}
// 判断用户是否为灰度用户
isGrayUser(userId) {
// 基于用户ID的哈希值决定是否为灰度用户
const hash = this.hashUserId(userId)
return hash < this.config.grayRatio
}
// 启动灰度发布
async startGrayRelease(version) {
console.log(`启动灰度发布 - 版本: ${version}`)
// 设置灰度配置
await this.setGrayConfiguration(version)
// 开始监控
this.startMonitoring()
// 设置自动检查
this.scheduleHealthCheck()
console.log(`灰度发布已启动,灰度比例: ${this.config.grayRatio * 100}%`)
}
// 监控灰度发布状态
startMonitoring() {
setInterval(() => {
this.collectMetrics()
this.analyzeMetrics()
}, 5 * 60 * 1000) // 每5分钟检查一次
}
// 收集指标数据
async collectMetrics() {
// 收集用户数据
this.metrics.totalUsers = await this.getTotalUsers()
this.metrics.grayUsers = await this.getGrayUsers()
// 收集错误率
this.metrics.errorRate = await this.getErrorRate()
// 收集崩溃率
this.metrics.crashRate = await this.getCrashRate()
// 收集性能数据
this.metrics.performanceScore = await this.getPerformanceScore()
console.log('灰度发布指标:', this.metrics)
}
// 分析指标数据
analyzeMetrics() {
const issues = []
// 检查错误率
if (this.metrics.errorRate > 0.05) { // 5%
issues.push(`错误率过高: ${(this.metrics.errorRate * 100).toFixed(2)}%`)
}
// 检查崩溃率
if (this.metrics.crashRate > 0.01) { // 1%
issues.push(`崩溃率过高: ${(this.metrics.crashRate * 100).toFixed(2)}%`)
}
// 检查性能分数
if (this.metrics.performanceScore < 80) {
issues.push(`性能分数过低: ${this.metrics.performanceScore}`)
}
if (issues.length > 0) {
console.warn('灰度发布发现问题:', issues)
this.handleGrayReleaseIssues(issues)
} else {
console.log('灰度发布状态正常')
}
}
// 处理灰度发布问题
async handleGrayReleaseIssues(issues) {
const severity = this.calculateSeverity(issues)
if (severity === 'critical') {
console.error('发现严重问题,立即回滚')
await this.rollbackGrayRelease()
} else if (severity === 'high') {
console.warn('发现高风险问题,暂停灰度发布')
await this.pauseGrayRelease()
} else {
console.log('发现一般问题,继续监控')
await this.sendAlert(issues)
}
}
// 扩大灰度范围
async expandGrayRelease(newRatio) {
if (newRatio > this.config.grayRatio) {
console.log(`扩大灰度范围: ${this.config.grayRatio * 100}% -> ${newRatio * 100}%`)
this.config.grayRatio = newRatio
await this.setGrayConfiguration()
// 通知相关人员
await this.sendNotification({
type: 'gray_expansion',
oldRatio: this.config.grayRatio,
newRatio: newRatio
})
}
}
// 完成灰度发布
async completeGrayRelease() {
console.log('完成灰度发布,全量发布')
// 设置全量发布
await this.setFullRelease()
// 生成灰度发布报告
const report = await this.generateGrayReleaseReport()
// 清理灰度配置
await this.cleanupGrayConfiguration()
console.log('灰度发布完成')
return report
}
// 回滚灰度发布
async rollbackGrayRelease() {
console.log('回滚灰度发布')
// 恢复到上一个稳定版本
await this.revertToPreviousVersion()
// 发送回滚通知
await this.sendRollbackNotification()
// 生成回滚报告
const report = await this.generateRollbackReport()
return report
}
// 辅助方法
hashUserId(userId) {
// 简单的哈希函数
let hash = 0
for (let i = 0; i < userId.length; i++) {
const char = userId.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // 转换为32位整数
}
return Math.abs(hash) / 2147483647 // 归一化到 0-1
}
async setGrayConfiguration(version) {
// 设置灰度配置
console.log('设置灰度配置')
}
scheduleHealthCheck() {
// 定期健康检查
setTimeout(() => {
this.performHealthCheck()
}, this.config.duration)
}
async performHealthCheck() {
// 执行健康检查
console.log('执行健康检查')
}
async getTotalUsers() { return 1000 }
async getGrayUsers() { return 100 }
async getErrorRate() { return 0.02 }
async getCrashRate() { return 0.005 }
async getPerformanceScore() { return 85 }
calculateSeverity(issues) {
// 根据问题计算严重程度
if (issues.some(issue => issue.includes('崩溃率'))) {
return 'critical'
} else if (issues.some(issue => issue.includes('错误率'))) {
return 'high'
} else {
return 'medium'
}
}
async pauseGrayRelease() {
console.log('暂停灰度发布')
}
async sendAlert(issues) {
console.log('发送告警:', issues)
}
async sendNotification(data) {
console.log('发送通知:', data)
}
async setFullRelease() {
console.log('设置全量发布')
}
async generateGrayReleaseReport() {
return {
startTime: new Date(),
endTime: new Date(),
grayRatio: this.config.grayRatio,
metrics: this.metrics,
issues: [],
success: true
}
}
async cleanupGrayConfiguration() {
console.log('清理灰度配置')
}
async revertToPreviousVersion() {
console.log('恢复到上一个版本')
}
async sendRollbackNotification() {
console.log('发送回滚通知')
}
async generateRollbackReport() {
return {
rollbackTime: new Date(),
reason: '灰度发布问题',
metrics: this.metrics
}
}
}
发布后监控
线上监控
javascript
// 线上监控系统
class ProductionMonitor {
constructor() {
this.alerts = []
this.metrics = new Map()
this.thresholds = {
errorRate: 0.05,
responseTime: 2000,
crashRate: 0.01,
memoryUsage: 100 * 1024 * 1024 // 100MB
}
}
// 启动监控
startMonitoring() {
console.log('启动线上监控...')
// 监控错误率
this.monitorErrorRate()
// 监控性能指标
this.monitorPerformance()
// 监控用户行为
this.monitorUserBehavior()
// 监控业务指标
this.monitorBusinessMetrics()
}
// 监控错误率
monitorErrorRate() {
setInterval(async () => {
const errorRate = await this.getErrorRate()
this.recordMetric('error_rate', errorRate)
if (errorRate > this.thresholds.errorRate) {
this.triggerAlert('high_error_rate', {
current: errorRate,
threshold: this.thresholds.errorRate
})
}
}, 60 * 1000) // 每分钟检查
}
// 监控性能指标
monitorPerformance() {
setInterval(async () => {
const metrics = await this.getPerformanceMetrics()
// 记录各项指标
Object.keys(metrics).forEach(key => {
this.recordMetric(`performance_${key}`, metrics[key])
})
// 检查响应时间
if (metrics.responseTime > this.thresholds.responseTime) {
this.triggerAlert('slow_response', {
current: metrics.responseTime,
threshold: this.thresholds.responseTime
})
}
// 检查内存使用
if (metrics.memoryUsage > this.thresholds.memoryUsage) {
this.triggerAlert('high_memory_usage', {
current: metrics.memoryUsage,
threshold: this.thresholds.memoryUsage
})
}
}, 5 * 60 * 1000) // 每5分钟检查
}
// 监控用户行为
monitorUserBehavior() {
setInterval(async () => {
const userMetrics = await this.getUserMetrics()
// 记录用户指标
this.recordMetric('active_users', userMetrics.activeUsers)
this.recordMetric('session_duration', userMetrics.avgSessionDuration)
this.recordMetric('bounce_rate', userMetrics.bounceRate)
// 检查异常用户行为
if (userMetrics.bounceRate > 0.8) {
this.triggerAlert('high_bounce_rate', {
current: userMetrics.bounceRate,
threshold: 0.8
})
}
}, 10 * 60 * 1000) // 每10分钟检查
}
// 监控业务指标
monitorBusinessMetrics() {
setInterval(async () => {
const businessMetrics = await this.getBusinessMetrics()
// 记录业务指标
Object.keys(businessMetrics).forEach(key => {
this.recordMetric(`business_${key}`, businessMetrics[key])
})
// 检查关键业务指标
this.checkBusinessThresholds(businessMetrics)
}, 15 * 60 * 1000) // 每15分钟检查
}
// 记录指标
recordMetric(name, value) {
if (!this.metrics.has(name)) {
this.metrics.set(name, [])
}
const metricData = this.metrics.get(name)
metricData.push({
value: value,
timestamp: Date.now()
})
// 保持最近1000个数据点
if (metricData.length > 1000) {
metricData.splice(0, metricData.length - 1000)
}
}
// 触发告警
triggerAlert(type, data) {
const alert = {
id: Date.now(),
type: type,
data: data,
timestamp: new Date().toISOString(),
severity: this.getAlertSeverity(type),
resolved: false
}
this.alerts.push(alert)
console.warn('触发告警:', alert)
// 发送告警通知
this.sendAlertNotification(alert)
// 自动处理告警
this.handleAlert(alert)
}
// 处理告警
async handleAlert(alert) {
switch (alert.type) {
case 'high_error_rate':
await this.handleHighErrorRate(alert)
break
case 'slow_response':
await this.handleSlowResponse(alert)
break
case 'high_memory_usage':
await this.handleHighMemoryUsage(alert)
break
case 'high_bounce_rate':
await this.handleHighBounceRate(alert)
break
default:
console.log('未知告警类型:', alert.type)
}
}
// 生成监控报告
generateMonitoringReport(timeRange = 24 * 60 * 60 * 1000) { // 24小时
const endTime = Date.now()
const startTime = endTime - timeRange
const report = {
timeRange: {
start: new Date(startTime).toISOString(),
end: new Date(endTime).toISOString()
},
metrics: {},
alerts: this.alerts.filter(alert =>
new Date(alert.timestamp).getTime() >= startTime
),
summary: {}
}
// 汇总指标数据
this.metrics.forEach((data, name) => {
const filteredData = data.filter(point =>
point.timestamp >= startTime
)
if (filteredData.length > 0) {
const values = filteredData.map(point => point.value)
report.metrics[name] = {
count: values.length,
avg: values.reduce((a, b) => a + b, 0) / values.length,
min: Math.min(...values),
max: Math.max(...values),
latest: values[values.length - 1]
}
}
})
// 生成摘要
report.summary = {
totalAlerts: report.alerts.length,
criticalAlerts: report.alerts.filter(a => a.severity === 'critical').length,
unresolvedAlerts: report.alerts.filter(a => !a.resolved).length,
avgErrorRate: report.metrics.error_rate?.avg || 0,
avgResponseTime: report.metrics.performance_responseTime?.avg || 0
}
console.log('监控报告:', report)
return report
}
// 辅助方法
async getErrorRate() {
// 实际实现中需要从监控系统获取错误率
return Math.random() * 0.1
}
async getPerformanceMetrics() {
// 实际实现中需要获取性能指标
return {
responseTime: Math.random() * 3000 + 500,
memoryUsage: Math.random() * 50 * 1024 * 1024 + 30 * 1024 * 1024,
cpuUsage: Math.random() * 80 + 10
}
}
async getUserMetrics() {
// 实际实现中需要获取用户指标
return {
activeUsers: Math.floor(Math.random() * 1000 + 500),
avgSessionDuration: Math.random() * 300 + 60,
bounceRate: Math.random() * 0.5 + 0.2
}
}
async getBusinessMetrics() {
// 实际实现中需要获取业务指标
return {
orders: Math.floor(Math.random() * 100 + 50),
revenue: Math.random() * 10000 + 5000,
conversion: Math.random() * 0.1 + 0.05
}
}
checkBusinessThresholds(metrics) {
// 检查业务指标阈值
if (metrics.conversion < 0.03) {
this.triggerAlert('low_conversion', {
current: metrics.conversion,
threshold: 0.03
})
}
}
getAlertSeverity(type) {
const severityMap = {
'high_error_rate': 'critical',
'slow_response': 'high',
'high_memory_usage': 'medium',
'high_bounce_rate': 'low',
'low_conversion': 'medium'
}
return severityMap[type] || 'low'
}
async sendAlertNotification(alert) {
// 发送告警通知
console.log('发送告警通知:', alert.type)
}
async handleHighErrorRate(alert) {
console.log('处理高错误率告警')
// 可能的处理措施:
// 1. 自动回滚到上一个版本
// 2. 限流保护
// 3. 通知开发团队
}
async handleSlowResponse(alert) {
console.log('处理响应慢告警')
// 可能的处理措施:
// 1. 扩容服务器
// 2. 优化数据库查询
// 3. 启用缓存
}
async handleHighMemoryUsage(alert) {
console.log('处理高内存使用告警')
// 可能的处理措施:
// 1. 重启服务
// 2. 清理缓存
// 3. 内存泄漏排查
}
async handleHighBounceRate(alert) {
console.log('处理高跳出率告警')
// 可能的处理措施:
// 1. 检查页面加载速度
// 2. 分析用户行为
// 3. 优化用户体验
}
}
发布最佳实践
发布检查清单
javascript
const deploymentChecklist = {
// 发布前检查
preDeployment: [
'代码审查已完成',
'单元测试通过率 > 90%',
'集成测试全部通过',
'性能测试达标',
'安全扫描无高危漏洞',
'配置文件已更新',
'数据库迁移脚本已准备',
'回滚方案已制定',
'发布说明已编写',
'相关人员已通知'
],
// 发布中检查
deployment: [
'构建过程无错误',
'代码上传成功',
'配置文件正确应用',
'数据库迁移成功',
'服务启动正常',
'健康检查通过',
'监控系统正常',
'日志输出正常'
],
// 发布后检查
postDeployment: [
'核心功能验证通过',
'用户访问正常',
'接口响应正常',
'数据一致性检查通过',
'性能指标正常',
'错误率在预期范围内',
'监控告警正常',
'用户反馈收集'
]
}
console.log('发布检查清单:', deploymentChecklist)
发布策略
javascript
// 发布策略配置
const deploymentStrategies = {
// 蓝绿发布
blueGreen: {
description: '维护两个相同的生产环境,一次性切换流量',
advantages: ['快速回滚', '零停机时间', '完整测试'],
disadvantages: ['资源消耗大', '数据同步复杂'],
suitableFor: ['关键业务系统', '用户量大的应用']
},
// 滚动发布
rolling: {
description: '逐步替换旧版本实例',
advantages: ['资源利用率高', '风险分散'],
disadvantages: ['发布时间长', '版本混合运行'],
suitableFor: ['微服务架构', '可水平扩展的应用']
},
// 金丝雀发布
canary: {
description: '先发布给少量用户,逐步扩大范围',
advantages: ['风险可控', '问题早发现', '用户影响小'],
disadvantages: ['发布流程复杂', '监控要求高'],
suitableFor: ['新功能发布', '重大版本更新']
},
// A/B 测试发布
abTesting: {
description: '同时运行多个版本,比较效果',
advantages: ['数据驱动决策', '用户体验优化'],
disadvantages: ['复杂度高', '数据分析要求高'],
suitableFor: ['功能优化', '用户体验改进']
}
}
总结
小程序发布部署是一个复杂的系统工程,需要考虑多个方面:
- 发布前准备 - 代码质量检查、性能测试、配置验证
- 版本管理 - 语义化版本控制、构建脚本、发布说明
- 发布流程 - 自动化发布、灰度发布、监控告警
- 发布后监控 - 线上监控、性能指标、用户反馈
- 最佳实践 - 发布检查清单、发布策略选择
通过建立完善的发布部署体系,可以确保小程序的稳定发布和持续改进,提供优质的用户体验。发布部署不仅是技术问题,更是流程和管理问题,需要团队协作和持续优化。