12 KiB
12 KiB
版本管理方案
🎯 问题
前端每次更新版本,后端也需要手动同步版本信息,容易出错且效率低。
✅ 解决方案
方案一:自动生成 version.json(已实现)⭐⭐⭐
前端打包时自动生成 version.json 文件,后端可以直接读取。
工作流程
1. 开发者修改 package.json 中的 version
↓
2. 运行 pnpm run build
↓
3. Vite 自动读取 package.json 版本
↓
4. 构建完成后生成 dist/version.json
↓
5. 部署到服务器
↓
6. 后端通过 HTTP 请求获取前端版本
生成的 version.json 格式
{
"version": "0.0.1",
"buildTime": "2025-12-30T10:30:00.000Z",
"gitCommit": "abc123",
"environment": "production"
}
后端获取版本的方式
方式 1:直接读取静态文件(推荐)
如果前端部署在 Nginx/CDN,后端可以直接访问:
curl https://your-domain.com/version.json
后端 API 实现示例:
// Node.js / Elysia 示例
app.get('/api/app/version', async (ctx) => {
// 从前端静态资源读取版本
const response = await fetch('https://your-frontend-domain.com/version.json')
const frontendVersion = await response.json()
const { platform, currentVersion } = ctx.query
return {
version: frontendVersion.version,
forceUpdate: compareVersion(currentVersion, frontendVersion.version) < 0,
updateMessage: '修复了一些问题',
updateUrl: platform === 'ios'
? 'https://apps.apple.com/app/xxx'
: 'https://play.google.com/store/apps/details?id=xxx',
minSupportVersion: '0.0.1',
}
})
方式 2:部署时同步到后端
在 CI/CD 中,部署前端时自动同步 version.json 到后端:
# GitHub Actions / GitLab CI 示例
- name: Deploy Frontend
run: |
pnpm run build
# 上传到前端服务器
rsync -avz dist/ user@frontend-server:/var/www/html/
# 同时将 version.json 复制到后端
scp dist/version.json user@backend-server:/app/frontend-version.json
后端直接读取本地文件:
import fs from 'fs'
app.get('/api/app/version', async (ctx) => {
const versionFile = fs.readFileSync('/app/frontend-version.json', 'utf-8')
const { version } = JSON.parse(versionFile)
return {
version,
forceUpdate: false,
// ...
}
})
📝 使用步骤
1. 修改版本号
只需修改 package.json 中的 version 字段:
{
"version": "1.2.3" // 只改这里
}
2. 构建项目
pnpm run build
构建完成后会在 dist/ 目录生成 version.json:
dist/
├── index.html
├── assets/
└── version.json ← 自动生成
3. 部署
将整个 dist/ 目录部署到服务器,version.json 会一起部署。
🔧 原生应用版本同步
iOS (Xcode)
修改 ios/App/App/Info.plist:
<key>CFBundleShortVersionString</key>
<string>1.2.3</string> <!-- 与 package.json 保持一致 -->
Android (build.gradle)
修改 android/app/build.gradle:
versionName "1.2.3" // 与 package.json 保持一致
自动化脚本(可选)
创建 scripts/sync-version.js:
#!/usr/bin/env node
import fs from 'fs'
import { execSync } from 'child_process'
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf-8'))
const version = packageJson.version
console.log(`Syncing version ${version} to native projects...`)
// iOS
const infoPlist = './ios/App/App/Info.plist'
let plistContent = fs.readFileSync(infoPlist, 'utf-8')
plistContent = plistContent.replace(
/<key>CFBundleShortVersionString<\/key>\s*<string>.*?<\/string>/,
`<key>CFBundleShortVersionString</key>\n\t<string>${version}</string>`
)
fs.writeFileSync(infoPlist, plistContent)
// Android (需要安装 gradle 解析库或手动更新)
console.log('Please manually update android/app/build.gradle versionName')
console.log('✓ Version synced!')
在 package.json 中添加脚本:
{
"scripts": {
"version:sync": "node scripts/sync-version.js",
"build:ios": "npm run version:sync && ionic cap sync ios",
"build:android": "npm run version:sync && ionic cap sync android"
}
}
🎨 版本号规范
遵循语义化版本 (Semantic Versioning):
主版本号.次版本号.修订号
↓ ↓ ↓
1 . 2 . 3
- 主版本号:不兼容的 API 修改
- 次版本号:向下兼容的功能新增
- 修订号:向下兼容的问题修正
版本更新示例
0.0.1→0.0.2- 修复 bug0.0.2→0.1.0- 新增功能0.1.0→1.0.0- 重大更新
🚀 CI/CD 集成
GitHub Actions 示例
name: Build and Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm run build
env:
NODE_ENV: production
- name: Deploy to server
run: |
rsync -avz dist/ user@server:/var/www/html/
- name: Notify backend
run: |
curl -X POST https://your-backend.com/api/webhook/frontend-deployed \
-H "Content-Type: application/json" \
-d '{"version": "'$(node -p "require('./package.json').version")'"}'
📊 方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| 自动生成 version.json | 完全自动化、前后端解耦 | 需要后端额外请求 | ⭐⭐⭐⭐⭐ |
| 手动同步 | 简单直接 | 容易忘记、出错 | ⭐ |
| 共享配置文件 | 统一管理 | 需要 Monorepo | ⭐⭐⭐ |
| 环境变量 | 灵活 | 部署时需手动设置 | ⭐⭐ |
✅ 总结
- 前端:只需修改
package.json的version字段 - 构建:运行
pnpm run build自动生成version.json - 后端:通过 HTTP 请求或读取文件获取版本信息
- 原生应用:可选使用脚本自动同步版本号
一次配置,永久受益! 🎉
后端 API 实现示例 - 版本检查接口
import { Elysia, t } from 'elysia'
// 版本比较工具函数
function compareVersion(version1: string, version2: string): number {
const v1Parts = version1.split('.').map(Number)
const v2Parts = version2.split('.').map(Number)
const maxLength = Math.max(v1Parts.length, v2Parts.length)
for (let i = 0; i < maxLength; i++) {
const v1Part = v1Parts[i] || 0
const v2Part = v2Parts[i] || 0
if (v1Part < v2Part) return -1
if (v1Part > v2Part) return 1
}
return 0
}
// 方案一:从前端静态资源读取版本(推荐)
async function getFrontendVersionFromURL(): Promise<{
version: string
buildTime: string
gitCommit: string
environment: string
}> {
const response = await fetch('https://your-frontend-domain.com/version.json')
return await response.json()
}
// 方案二:从本地文件读取(适用于前后端部署在同一服务器)
import fs from 'fs'
function getFrontendVersionFromFile(): {
version: string
buildTime: string
gitCommit: string
environment: string
} {
const content = fs.readFileSync('/app/frontend-version.json', 'utf-8')
return JSON.parse(content)
}
// 应用商店链接配置
const APP_STORE_URLS = {
ios: 'https://apps.apple.com/app/id123456789',
android: 'https://play.google.com/store/apps/details?id=riwa.ionic.app',
}
// 版本策略配置(可存储在数据库)
const VERSION_POLICIES = {
minSupportVersion: '0.0.1', // 最低支持版本
forceUpdateVersions: ['0.0.1'], // 需要强制更新的版本列表
updateMessages: {
'zh-CN': '修复了一些问题并优化了性能',
'en-US': 'Bug fixes and performance improvements',
},
}
// Elysia 路由定义
const app = new Elysia()
.get(
'/api/app/version',
async ({ query, headers }) => {
const { platform, currentVersion } = query
const lang = headers['accept-language']?.split(',')[0] || 'en-US'
try {
// 获取前端版本信息
const frontendVersion = await getFrontendVersionFromURL()
// 或使用本地文件: const frontendVersion = getFrontendVersionFromFile()
// 判断是否需要更新
const hasUpdate = compareVersion(currentVersion, frontendVersion.version) < 0
// 判断是否强制更新
let forceUpdate = VERSION_POLICIES.forceUpdateVersions.includes(currentVersion)
// 检查是否低于最低支持版本
if (compareVersion(currentVersion, VERSION_POLICIES.minSupportVersion) < 0) {
forceUpdate = true
}
// 获取更新链接
const updateUrl = platform === 'ios'
? APP_STORE_URLS.ios
: platform === 'android'
? APP_STORE_URLS.android
: ''
// 获取更新说明(多语言)
const updateMessage = VERSION_POLICIES.updateMessages[lang]
|| VERSION_POLICIES.updateMessages['en-US']
return {
version: frontendVersion.version,
buildNumber: parseInt(frontendVersion.version.replace(/\./g, '')),
buildTime: frontendVersion.buildTime,
gitCommit: frontendVersion.gitCommit,
forceUpdate,
updateMessage,
updateUrl,
minSupportVersion: VERSION_POLICIES.minSupportVersion,
releaseNotes: [
'修复了已知问题',
'优化了应用性能',
'改进了用户界面',
],
}
} catch (error) {
console.error('Failed to get frontend version:', error)
// 降级处理:返回当前版本,不强制更新
return {
version: currentVersion,
forceUpdate: false,
updateMessage: '',
updateUrl: '',
}
}
},
{
query: t.Object({
platform: t.Union([t.Literal('ios'), t.Literal('android'), t.Literal('web')]),
currentVersion: t.String(),
}),
response: t.Object({
version: t.String(),
buildNumber: t.Optional(t.Number()),
buildTime: t.Optional(t.String()),
gitCommit: t.Optional(t.String()),
forceUpdate: t.Boolean(),
updateMessage: t.Optional(t.String()),
updateUrl: t.Optional(t.String()),
minSupportVersion: t.Optional(t.String()),
releaseNotes: t.Optional(t.Array(t.String())),
}),
}
)
export default app
/**
* 使用示例:
*
* 1. 启动后端服务
* 2. 前端请求:GET /api/app/version?platform=ios¤tVersion=0.0.1
* 3. 后端响应:
* {
* "version": "0.0.2",
* "buildTime": "2025-12-30T11:42:43.425Z",
* "forceUpdate": false,
* "updateMessage": "修复了一些问题并优化了性能",
* "updateUrl": "https://apps.apple.com/app/id123456789",
* "minSupportVersion": "0.0.1"
* }
*/
/**
* 数据库存储方案(可选)
*
* 如果需要更灵活的版本策略管理,可以将配置存储在数据库:
*
* CREATE TABLE app_versions (
* id SERIAL PRIMARY KEY,
* platform VARCHAR(20) NOT NULL,
* version VARCHAR(20) NOT NULL,
* min_support_version VARCHAR(20),
* force_update BOOLEAN DEFAULT FALSE,
* update_message_zh TEXT,
* update_message_en TEXT,
* update_url TEXT,
* release_notes JSONB,
* created_at TIMESTAMP DEFAULT NOW()
* );
*
* 然后从数据库查询版本策略:
* const policy = await db.query(
* 'SELECT * FROM app_versions WHERE platform = $1 ORDER BY created_at DESC LIMIT 1',
* [platform]
* )
*/