Skip to content

Commit 887d567

Browse files
committed
feat:新增跨版本 ObjectId 兼容性(支持 mongoose bson@4.x/5.x)
1 parent 8675001 commit 887d567

10 files changed

Lines changed: 1129 additions & 9 deletions

CHANGELOG.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# 变更日志 (CHANGELOG)
22

33
> **说明**: 版本概览摘要,详细变更见 [changelogs/](./changelogs/) 目录
4-
> **最后更新**: 2026-01-21
4+
> **最后更新**: 2026-01-27
55
66
---
77

88
## 版本概览
99

1010
| 版本 | 日期 | 变更摘要 | 详细 |
1111
|------|------|---------|------|
12+
| [v1.1.1](#v111---objectid-跨版本兼容) | 2026-01-27 | 🔧 Bug修复:新增跨版本 ObjectId 兼容性(支持 mongoose bson@4.x/5.x)| [查看](#v111---objectid-跨版本兼容) |
1213
| [v1.1.0](./changelogs/v1.1.0.md) | 2026-01-21 | 🎉 重大更新:新增49个操作符,实现100% MongoDB操作符支持(122/122)| [查看](./changelogs/v1.1.0.md) |
1314
| [v1.0.9](./changelogs/v1.0.9.md) | 2026-01-19 | 🎉 重大功能:统一表达式系统 - 67个操作符 + 107个测试 (100%通过) | [查看](./changelogs/v1.0.9.md) |
1415
| [v1.0.8](./changelogs/v1.0.8.md) | 2026-01-17 | 🎉 重大功能:多连接池 + Update 聚合管道 + Saga 事务 + Change Stream 同步 | [查看](./changelogs/v1.0.8.md) |
@@ -33,6 +34,49 @@
3334

3435
## 里程碑版本
3536

37+
### v1.1.1 - ObjectId 跨版本兼容 🔧
38+
39+
**发布日期**: 2026-01-27
40+
**重要性**: ⭐⭐⭐⭐
41+
42+
**问题背景**:
43+
- 混用 mongoose (bson@4.x/5.x) 和 monSQLize (bson@6.x) 时出现版本冲突
44+
- 错误信息:`Unsupported BSON version, bson types must be from bson 6.x.x`
45+
- 跨服务数据传递时 ObjectId 实例无法被 mongodb@6.x 驱动识别
46+
47+
**解决方案**:
48+
-**自动跨版本转换**: 检测并转换来自其他 BSON 版本的 ObjectId
49+
-**递归处理**: 自动处理嵌套对象和数组中的 ObjectId
50+
-**性能优化**: 无需转换时返回原对象(零拷贝)
51+
-**错误降级**: 转换失败时返回原值,不影响其他字段
52+
-**调试支持**: 提供详细的转换日志
53+
54+
**兼容性**:
55+
| BSON 版本 | mongoose 版本 | 支持状态 |
56+
|-----------|--------------|---------|
57+
| bson@4.x | mongoose@5.x | ✅ 完全支持 |
58+
| bson@5.x | mongoose@6.x | ✅ 完全支持 |
59+
| bson@6.x | mongoose@7.x | ✅ 原生支持 |
60+
61+
**测试覆盖**:
62+
- 🏆 **测试数量**: 17个测试用例
63+
- 🏆 **通过率**: 100% (17/17通过)
64+
- 🏆 **覆盖场景**: 基础转换、嵌套对象、数组、边界情况、错误处理
65+
66+
**使用示例**:
67+
```javascript
68+
// 从 mongoose 获取数据(包含 bson@4.x/5.x 的 ObjectId)
69+
const dataFromMongoose = await MongooseModel.findOne({ ... }).lean();
70+
71+
// 直接插入,monSQLize 自动转换 ObjectId
72+
const result = await msq.collection('orders').insertOne(dataFromMongoose);
73+
// ✅ 成功:自动将旧版本 ObjectId 转换为 bson@6.x 版本
74+
```
75+
76+
**详细文档**: [查看 docs/objectid-cross-version.md](./docs/objectid-cross-version.md)
77+
78+
---
79+
3680
### v1.0.9 - 统一表达式系统 🎉
3781

3882
**发布日期**: 2026-01-19

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,42 @@ await db.close(); // 自动关闭SSH隧道
944944

945945
---
946946

947-
### 10. 🎯 Model 层 - 像 ORM 一样使用(v1.0.3+)
947+
### 10. 🔄 ObjectId 跨版本兼容(v1.1.1+)🆕
948+
949+
解决混用 mongoose 和 monSQLize 时的 BSON 版本冲突问题。
950+
951+
```javascript
952+
// ❌ 问题场景:mongoose (bson@4.x/5.x) + monSQLize (bson@6.x)
953+
const dataFromMongoose = await MongooseModel.findOne({ ... }).lean();
954+
await msq.collection('orders').insertOne(dataFromMongoose);
955+
// 错误:Unsupported BSON version, bson types must be from bson 6.x.x
956+
957+
// ✅ monSQLize v1.1.1+ 自动处理
958+
const dataFromMongoose = await MongooseModel.findOne({ ... }).lean();
959+
await msq.collection('orders').insertOne(dataFromMongoose);
960+
// 成功:自动将旧版本 ObjectId 转换为 bson@6.x
961+
```
962+
963+
**特性**
964+
- ✅ 自动检测并转换来自其他 BSON 版本的 ObjectId
965+
- ✅ 递归处理嵌套对象和数组
966+
- ✅ 性能优化:无需转换时零拷贝
967+
- ✅ 错误降级:转换失败不影响其他字段
968+
- ✅ 完全透明:无需修改业务代码
969+
970+
**兼容性**
971+
972+
| BSON 版本 | mongoose 版本 | 支持状态 |
973+
|-----------|--------------|---------|
974+
| bson@4.x | mongoose@5.x | ✅ 完全支持 |
975+
| bson@5.x | mongoose@6.x | ✅ 完全支持 |
976+
| bson@6.x | mongoose@7.x | ✅ 原生支持 |
977+
978+
[📖 完整文档](./docs/objectid-cross-version.md)
979+
980+
---
981+
982+
### 11. 🎯 Model 层 - 像 ORM 一样使用(v1.0.3+)
948983

949984
monSQLize 提供了一个轻量级的 Model 层,让你可以像使用 ORM 一样定义数据模型,同时保持 MongoDB 的灵活性。
950985

docs/objectid-cross-version-faq.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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

Comments
 (0)