# 版本管理方案

## 🎯 问题

前端每次更新版本，后端也需要手动同步版本信息，容易出错且效率低。

## ✅ 解决方案

### 方案一：自动生成 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 格式

```json
{
  "version": "0.0.1",
  "buildTime": "2025-12-30T10:30:00.000Z",
  "gitCommit": "abc123",
  "environment": "production"
}
```

#### 后端获取版本的方式

**方式 1：直接读取静态文件（推荐）**

如果前端部署在 Nginx/CDN，后端可以直接访问：

```bash
curl https://your-domain.com/version.json
```

后端 API 实现示例：

```typescript
// 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 到后端：

```yaml
# 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
```

后端直接读取本地文件：

```typescript
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` 字段：

```json
{
  "version": "1.2.3"  // 只改这里
}
```

### 2. 构建项目

```bash
pnpm run build
```

构建完成后会在 `dist/` 目录生成 `version.json`：

```
dist/
├── index.html
├── assets/
└── version.json  ← 自动生成
```

### 3. 部署

将整个 `dist/` 目录部署到服务器，`version.json` 会一起部署。

---

## 🔧 原生应用版本同步

### iOS (Xcode)

修改 `ios/App/App/Info.plist`：

```xml
<key>CFBundleShortVersionString</key>
<string>1.2.3</string>  <!-- 与 package.json 保持一致 -->
```

### Android (build.gradle)

修改 `android/app/build.gradle`：

```gradle
versionName "1.2.3"  // 与 package.json 保持一致
```

### 自动化脚本（可选）

创建 `scripts/sync-version.js`：

```javascript
#!/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` 中添加脚本：

```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` - 修复 bug
- `0.0.2` → `0.1.0` - 新增功能
- `0.1.0` → `1.0.0` - 重大更新

---

## 🚀 CI/CD 集成

### GitHub Actions 示例

```yaml
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 | ⭐⭐⭐ |
| 环境变量 | 灵活 | 部署时需手动设置 | ⭐⭐ |

---

## ✅ 总结

1. **前端**：只需修改 `package.json` 的 `version` 字段
2. **构建**：运行 `pnpm run build` 自动生成 `version.json`
3. **后端**：通过 HTTP 请求或读取文件获取版本信息
4. **原生应用**：可选使用脚本自动同步版本号

**一次配置，永久受益！** 🎉


## 后端 API 实现示例 - 版本检查接口

```ts
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&currentVersion=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]
 * )
 */

```