|
| 1 | +# ObjectId 跨版本兼容性 - 常见问题解答(FAQ) |
| 2 | + |
| 3 | +## Q1: 为什么连接时提示"[DEBUG] [Saga] 使用 Redis 存储",明明没连接 Redis? |
| 4 | + |
| 5 | +### 问题描述 |
| 6 | + |
| 7 | +即使没有配置 Redis,初始化 monSQLize 时仍然输出: |
| 8 | +``` |
| 9 | +[DEBUG] [Saga] 使用 Redis 存储(多进程共享) |
| 10 | +``` |
| 11 | + |
| 12 | +### 根本原因 |
| 13 | + |
| 14 | +monSQLize 的 Saga 协调器(SagaOrchestrator)判断逻辑有误: |
| 15 | +- **错误逻辑**:只要有 `cache` 实例且有 `set()` 方法,就认为是 Redis |
| 16 | +- **实际情况**:monSQLize 默认启用内存缓存(MemoryCache),也有 `set()` 方法 |
| 17 | + |
| 18 | +### 解决方案 |
| 19 | + |
| 20 | +修改 `lib/saga/SagaOrchestrator.js` 的判断逻辑,通过检测 Redis 特有的方法来识别: |
| 21 | + |
| 22 | +```javascript |
| 23 | +// ❌ 旧逻辑(错误) |
| 24 | +if (this.cache && typeof this.cache.set === 'function') { |
| 25 | + this.useRedis = true; |
| 26 | + this.logger?.debug('[Saga] 使用 Redis 存储(多进程共享)'); |
| 27 | +} |
| 28 | + |
| 29 | +// ✅ 新逻辑(正确) |
| 30 | +const isRedis = typeof this.cache.keys === 'function' && |
| 31 | + typeof this.cache.publish === 'function'; |
| 32 | + |
| 33 | +if (isRedis) { |
| 34 | + this.useRedis = true; |
| 35 | + this.logger?.debug('[Saga] 使用 Redis 存储(多进程共享)'); |
| 36 | +} else { |
| 37 | + this.sagas = new Map(); |
| 38 | + this.useRedis = false; |
| 39 | + this.logger?.debug('[Saga] 使用内存缓存(单进程,Saga 元数据不共享)'); |
| 40 | +} |
| 41 | +``` |
| 42 | +
|
| 43 | +### 验证结果 |
| 44 | +
|
| 45 | +修复后的日志输出: |
| 46 | +``` |
| 47 | +[DEBUG] [Saga] 使用内存缓存(单进程,Saga 元数据不共享) ✅ 正确 |
| 48 | +``` |
| 49 | +
|
| 50 | +--- |
| 51 | +
|
| 52 | +## Q2: 自动将旧版本 ObjectId 转换为 bson@6.x,会不会影响旧版本 mongoose? |
| 53 | +
|
| 54 | +### 问题描述 |
| 55 | +
|
| 56 | +担心 monSQLize 转换 ObjectId 后,mongoose 读取数据时会出现问题。 |
| 57 | +
|
| 58 | +### 简短回答 |
| 59 | +
|
| 60 | +**不会有任何影响!完全向后兼容。** ✅ |
| 61 | +
|
| 62 | +### 详细解释 |
| 63 | +
|
| 64 | +#### 1. 转换的时机和方向 |
| 65 | +
|
| 66 | +``` |
| 67 | +┌─────────────┐ ┌─────────────┐ |
| 68 | +│ mongoose │ ─────────────────→│ MongoDB │ |
| 69 | +│ (bson@4.x) │ 写入 (无需转换) │ 数据库 │ |
| 70 | +└─────────────┘ └─────────────┘ |
| 71 | + ↑ ↓ |
| 72 | + ┌────────────────────────────────┘ └────────────────────────────────┐ |
| 73 | + │ │ |
| 74 | + 读取(自动) 写入(转换) |
| 75 | + │ │ |
| 76 | + ↓ ↓ |
| 77 | +┌─────────────┐ ┌─────────────┐ |
| 78 | +│ mongoose │ ←──────────────────────────────────────────────── │ monSQLize │ |
| 79 | +│ (bson@4.x) │ ✅ 正常读取,无需monSQLize干预 │ (bson@6.x) │ |
| 80 | +└─────────────┘ └─────────────┘ |
| 81 | +``` |
| 82 | +
|
| 83 | +**关键点**: |
| 84 | +- ✅ **转换是单向的**:只在 monSQLize 写入时发生 |
| 85 | +- ✅ **数据库存储统一**:ObjectId 的 BSON 二进制格式是标准的(12字节) |
| 86 | +- ✅ **mongoose 读取自动**:mongoose 读取时会自动将 BSON 转换为它自己的 ObjectId |
| 87 | +
|
| 88 | +#### 2. ObjectId 的存储格式 |
| 89 | +
|
| 90 | +无论是 bson@4.x、5.x 还是 6.x,ObjectId 在 MongoDB 中的存储格式都是相同的: |
| 91 | +
|
| 92 | +``` |
| 93 | +BSON 二进制格式(12 字节): |
| 94 | +┌─────────────┬─────────┬─────────┬─────────┐ |
| 95 | +│ Timestamp │ Machine │ Process │ Counter │ |
| 96 | +│ (4 bytes) │(3 bytes)│(2 bytes)│(3 bytes)│ |
| 97 | +└─────────────┴─────────┴─────────┴─────────┘ |
| 98 | +``` |
| 99 | +
|
| 100 | +**关键点**: |
| 101 | +- 所有 BSON 版本都遵循相同的规范 |
| 102 | +- MongoDB 不关心 ObjectId 是哪个 BSON 版本创建的 |
| 103 | +- 读取时,各库会将 BSON 转换为自己的 ObjectId 实例 |
| 104 | +
|
| 105 | +#### 3. 实际测试验证 |
| 106 | +
|
| 107 | +我们编写了完整的向后兼容性测试(`scripts/test/verify-backward-compatibility.js`): |
| 108 | +
|
| 109 | +**测试流程**: |
| 110 | +1. ✅ monSQLize 插入数据(包含旧版本 ObjectId,自动转换为 bson@6.x) |
| 111 | +2. ✅ 原生驱动读取(模拟 mongoose),验证 ObjectId 值和类型 |
| 112 | +3. ✅ 原生驱动更新数据(模拟 mongoose 写入) |
| 113 | +4. ✅ monSQLize 读取更新后的数据,验证一致性 |
| 114 | +
|
| 115 | +**测试结论**: |
| 116 | +``` |
| 117 | +✅ monSQLize 写入的数据可以被原生驱动正常读取 |
| 118 | +✅ ObjectId 值完全一致(十六进制字符串相同) |
| 119 | +✅ ObjectId 类型正确(都是 ObjectId 实例) |
| 120 | +✅ 原生驱动(mongoose)写入的数据 monSQLize 可以正常读取 |
| 121 | +✅ 混用 monSQLize 和 mongoose 不会有任何问题 |
| 122 | +``` |
| 123 | +
|
| 124 | +#### 4. 为什么不会影响 mongoose? |
| 125 | +
|
| 126 | +**核心原理**: |
| 127 | +
|
| 128 | +1. **转换只影响写入** |
| 129 | + - monSQLize 写入时:旧版本 ObjectId → 新版本 ObjectId → BSON(12字节) |
| 130 | + - mongoose 写入时:旧版本 ObjectId → BSON(12字节) |
| 131 | + - **存储结果相同**:都是标准的 BSON 格式 |
| 132 | +
|
| 133 | +2. **读取时各自转换** |
| 134 | + - mongoose 读取:BSON(12字节)→ mongoose 的 ObjectId(bson@4.x/5.x) |
| 135 | + - monSQLize 读取:BSON(12字节)→ monSQLize 的 ObjectId(bson@6.x) |
| 136 | + - **各自独立**:互不干扰 |
| 137 | +
|
| 138 | +3. **ObjectId 值始终一致** |
| 139 | + ```javascript |
| 140 | + // monSQLize 写入 |
| 141 | + const legacyId = mongoose.Types.ObjectId('507f1f77bcf86cd799439011'); |
| 142 | + await msq.collection('users').insertOne({ userId: legacyId }); |
| 143 | + // 存储到 MongoDB: BSON(507f1f77bcf86cd799439011) |
| 144 | + |
| 145 | + // mongoose 读取 |
| 146 | + const user = await User.findOne({ _id: ... }); |
| 147 | + console.log(user.userId.toString()); // "507f1f77bcf86cd799439011" ✅ |
| 148 | + |
| 149 | + // monSQLize 读取 |
| 150 | + const user2 = await msq.collection('users').findOne({ _id: ... }); |
| 151 | + console.log(user2.userId.toString()); // "507f1f77bcf86cd799439011" ✅ |
| 152 | + ``` |
| 153 | +
|
| 154 | +#### 5. 实际使用场景 |
| 155 | +
|
| 156 | +**场景 1:mongoose 服务 → monSQLize 服务(跨服务调用)** |
| 157 | +
|
| 158 | +```javascript |
| 159 | +// 服务 A(使用 mongoose) |
| 160 | +const user = await User.findOne({ username: 'john' }).lean(); |
| 161 | +// user.userId 是 mongoose 的 ObjectId (bson@4.x/5.x) |
| 162 | + |
| 163 | +// 调用服务 B(使用 monSQLize) |
| 164 | +await axios.post('http://service-b/api/orders', { userId: user.userId }); |
| 165 | + |
| 166 | +// 服务 B 接收数据 |
| 167 | +app.post('/api/orders', async (req, res) => { |
| 168 | + const { userId } = req.body; // 旧版本 ObjectId |
| 169 | + |
| 170 | + // ✅ monSQLize 自动转换 |
| 171 | + await msq.collection('orders').insertOne({ |
| 172 | + userId, // 自动转换为 bson@6.x |
| 173 | + productId: new ObjectId(), |
| 174 | + status: 'pending' |
| 175 | + }); |
| 176 | +}); |
| 177 | +``` |
| 178 | +
|
| 179 | +**场景 2:mongoose 和 monSQLize 混用同一个数据库** |
| 180 | +
|
| 181 | +```javascript |
| 182 | +// mongoose 写入 |
| 183 | +await User.create({ username: 'alice', age: 25 }); |
| 184 | + |
| 185 | +// monSQLize 读取 |
| 186 | +const users = await msq.collection('users').find({ age: { $gte: 18 } }); |
| 187 | +// ✅ 完全正常 |
| 188 | + |
| 189 | +// monSQLize 写入 |
| 190 | +await msq.collection('users').insertOne({ username: 'bob', age: 30 }); |
| 191 | + |
| 192 | +// mongoose 读取 |
| 193 | +const bob = await User.findOne({ username: 'bob' }); |
| 194 | +// ✅ 完全正常 |
| 195 | +``` |
| 196 | +
|
| 197 | +### 总结 |
| 198 | +
|
| 199 | +| 问题 | 回答 | |
| 200 | +|------|------| |
| 201 | +| 会不会影响 mongoose 读取? | ❌ 不会,mongoose 读取时会自动转换 | |
| 202 | +| 会不会影响数据库中的数据? | ❌ 不会,存储格式完全相同 | |
| 203 | +| 需要修改 mongoose 代码吗? | ❌ 不需要,完全透明 | |
| 204 | +| 能否混用 mongoose 和 monSQLize? | ✅ 可以,完全兼容 | |
| 205 | +| 转换有性能影响吗? | ✅ 极小(~0.01ms/ObjectId) | |
| 206 | +| 会不会丢失数据? | ❌ 不会,ObjectId 值完全一致 | |
| 207 | +
|
| 208 | +--- |
| 209 | +
|
| 210 | +## Q3: 如何验证我的项目是否存在兼容性问题? |
| 211 | +
|
| 212 | +运行以下测试脚本: |
| 213 | +
|
| 214 | +```bash |
| 215 | +# 跨版本兼容性测试 |
| 216 | +node scripts/test/verify-cross-version-objectid.js |
| 217 | + |
| 218 | +# 向后兼容性测试 |
| 219 | +node scripts/test/verify-backward-compatibility.js |
| 220 | +``` |
| 221 | +
|
| 222 | +--- |
| 223 | +
|
| 224 | +## Q4: 如果我不想自动转换,可以禁用吗? |
| 225 | +
|
| 226 | +目前自动转换是默认启用的,暂无配置项禁用。 |
| 227 | +
|
| 228 | +**原因**: |
| 229 | +- 这是一个 Bug 修复,不是新功能 |
| 230 | +- 转换是安全的,不会影响任何现有功能 |
| 231 | +- 性能影响极小(~0.01ms/ObjectId) |
| 232 | +
|
| 233 | +如果您确实需要禁用,请提交 Issue 说明您的场景。 |
| 234 | +
|
| 235 | +--- |
| 236 | +
|
| 237 | +## Q5: 如果遇到其他 BSON 类型冲突,如何处理? |
| 238 | +
|
| 239 | +目前只处理了 ObjectId 的跨版本兼容。如果遇到其他类型(如 Decimal128, Binary 等)的冲突,请: |
| 240 | +
|
| 241 | +1. 提交 Issue:https://github.com/vextjs/monSQLize/issues |
| 242 | +2. 提供复现步骤和错误信息 |
| 243 | +3. 我们会优先处理 |
| 244 | +
|
| 245 | +--- |
| 246 | +
|
| 247 | +## 相关文档 |
| 248 | +
|
| 249 | +- [ObjectId 跨版本兼容性指南](../docs/objectid-cross-version.md) |
| 250 | +- [修复报告](../reports/jrpc/implementation/objectid-cross-version-fix-v1.1.1.md) |
| 251 | +- [CHANGELOG](../CHANGELOG.md) |
0 commit comments