# 数据库迁移系统

## 概述

本项目使用版本化的数据库迁移系统，支持增量更新和自动同步。

## 特性

- ✅ 版本管理：每个迁移有唯一版本号
- ✅ 增量执行：只执行未执行的迁移
- ✅ 自动运行：服务器启动时自动执行迁移
- ✅ 幂等性：重复执行不会出错
- ✅ 迁移记录：记录所有已执行的迁移

## 迁移文件命名规范

```
XXXX-description.js
```

- `XXXX`: 4位数字版本号（如 0001, 0002）
- `description`: 迁移描述（使用短横线分隔）

示例：
- `0001-add-danmaku-fields.js`
- `0002-add-user-avatar.js`

## 创建新迁移

### 1. 创建迁移文件

在 `backend/migrations/` 目录下创建新文件：

```javascript
/**
 * 迁移: 添加用户头像字段
 * 版本: 0002
 * 日期: 2025-01-09
 */

module.exports = {
  async up(manager) {
    console.log('  - 添加 avatar 字段...');
    await manager.executeSql(`
      ALTER TABLE Users ADD COLUMN avatar TEXT;
    `);
  },

  async down(manager) {
    // 回滚逻辑（可选）
    console.log('  - 回滚不支持');
  }
};
```

### 2. 迁移方法

#### `up(manager)` - 升级
执行数据库变更的方法。

可用的 manager 方法：
- `executeSql(sql)`: 执行SQL语句
- `db`: 直接访问数据库连接

#### `down(manager)` - 回滚（可选）
回滚数据库变更的方法。

注意：SQLite 不支持 DROP COLUMN，回滚可能需要重建表。

## 执行迁移

### 自动执行（推荐）

服务器启动时会自动执行所有未执行的迁移：

```bash
# 开发环境
cd backend
npm start

# 生产环境（Docker）
docker-compose up -d
# 或
docker-compose restart
```

### 手动执行

#### 在容器内执行：

```bash
# Windows
migrate.bat

# Linux/Mac
chmod +x migrate.sh
./migrate.sh
```

#### 直接在容器中执行：

```bash
docker exec quanyoumi-app node /app/backend/migrations/index.js
```

## 迁移状态

迁移执行记录保存在 `migrations` 表中：

```sql
SELECT * FROM migrations;
```

输出示例：
```
id | version | name                  | executed_at
---+---------+-----------------------+---------------------
1  | 1       | add-danmaku-fields    | 2025-01-09 10:30:00
2  | 2       | add-user-avatar       | 2025-01-09 11:00:00
```

## 常见问题

### Q: 迁移失败怎么办？

A: 检查错误日志，修复问题后重新启动服务器。已执行的迁移不会重复执行。

### Q: 如何回滚迁移？

A: 目前不支持自动回滚。如需回滚，请手动修改数据库或创建新的迁移来撤销更改。

### Q: 可以修改已执行的迁移吗？

A: 不建议。已执行的迁移不应修改。如需更改，请创建新的迁移。

### Q: 迁移在什么时候执行？

A: 服务器启动时自动执行。也可以手动运行 `migrate.bat` 或 `migrate.sh`。

## 最佳实践

1. **版本号递增**：新迁移的版本号应大于所有现有迁移
2. **描述清晰**：文件名和注释应清楚说明迁移目的
3. **测试迁移**：在开发环境测试后再部署到生产环境
4. **幂等性**：迁移应该可以安全地重复执行
5. **小步迭代**：每个迁移只做一件事，便于追踪和回滚

## 示例

### 添加新字段

```javascript
module.exports = {
  async up(manager) {
    await manager.executeSql(`
      ALTER TABLE Activities ADD COLUMN featured INTEGER DEFAULT 0;
    `);
  }
};
```

### 创建新表

```javascript
module.exports = {
  async up(manager) {
    await manager.executeSql(`
      CREATE TABLE IF NOT EXISTS Comments (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        activityId INTEGER NOT NULL,
        userId INTEGER NOT NULL,
        content TEXT NOT NULL,
        createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
      );
    `);
  }
};
```

### 修改数据

```javascript
module.exports = {
  async up(manager) {
    await manager.executeSql(`
      UPDATE Activities SET status = 'completed' WHERE endTime < datetime('now');
    `);
  }
};
```
