Skip to content

Commit 2905d33

Browse files
committed
feat: add findAndCount core implementation
1 parent ae5b0eb commit 2905d33

3 files changed

Lines changed: 182 additions & 5 deletions

File tree

‎lib/mongodb/index.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ const {
1212
createFindOps,
1313
createFindOneOps,
1414
createFindOneByIdOps, // findOneById 快捷方法
15-
createFindByIdsOps, // 新增:findByIds 快捷方法
15+
createFindByIdsOps, // findByIds 快捷方法
16+
createFindAndCountOps, // 新增:findAndCount 快捷方法
1617
createCountOps,
1718
createAggregateOps,
1819
createDistinctOps,
@@ -228,7 +229,8 @@ module.exports = class {
228229
// 基础查询方法
229230
...createFindOneOps(moduleContext),
230231
...createFindOneByIdOps(moduleContext), // findOneById 便利方法
231-
...createFindByIdsOps(moduleContext), // 新增:findByIds 便利方法
232+
...createFindByIdsOps(moduleContext), // findByIds 便利方法
233+
...createFindAndCountOps(moduleContext), // 新增:findAndCount 便利方法
232234
...createFindOps(moduleContext),
233235
// 聚合与统计方法
234236
...createCountOps(moduleContext),
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* findAndCount 查询操作模块
3+
* @description 便利方法:同时返回数据和总数
4+
*/
5+
6+
const { createError, ErrorCodes } = require('../../errors');
7+
8+
/**
9+
* 创建 findAndCount 操作
10+
* @param {Object} context - 上下文对象
11+
* @returns {Function} findAndCount 方法
12+
*/
13+
function createFindAndCountOps(context) {
14+
const {
15+
collection,
16+
defaults,
17+
instanceId,
18+
effectiveDbName,
19+
logger,
20+
emit,
21+
mongoSlowLogShaper,
22+
cache,
23+
type
24+
} = context;
25+
26+
/**
27+
* 查询数据并返回总数(同时执行)
28+
* @param {Object} [query={}] - 查询条件
29+
* @param {Object} [options={}] - 查询选项
30+
* @param {Object} [options.projection] - 字段投影
31+
* @param {Object} [options.sort] - 排序方式
32+
* @param {number} [options.limit] - 限制返回数量
33+
* @param {number} [options.skip] - 跳过数量
34+
* @param {number} [options.cache] - 缓存时间(毫秒)
35+
* @param {number} [options.maxTimeMS] - 查询超时(毫秒)
36+
* @param {string} [options.comment] - 查询注释
37+
* @returns {Promise<Object>} { data, total }
38+
*
39+
* @example
40+
* // 基础用法
41+
* const { data, total } = await collection('users').findAndCount(
42+
* { status: 'active' },
43+
* { limit: 10, skip: 0 }
44+
* );
45+
*
46+
* @example
47+
* // 分页查询
48+
* const page = 1;
49+
* const pageSize = 20;
50+
* const { data, total } = await collection('users').findAndCount(
51+
* { role: 'user' },
52+
* { limit: pageSize, skip: (page - 1) * pageSize }
53+
* );
54+
* const totalPages = Math.ceil(total / pageSize);
55+
*/
56+
const findAndCount = async function findAndCount(query = {}, options = {}) {
57+
const startTime = Date.now();
58+
59+
// 1. 参数验证
60+
if (query !== null && typeof query !== 'object' || Array.isArray(query)) {
61+
throw createError(
62+
ErrorCodes.INVALID_ARGUMENT,
63+
'query 必须是对象',
64+
[{ field: 'query', type: 'type', message: 'query 必须是对象', received: typeof query }]
65+
);
66+
}
67+
68+
// 2. 提取选项
69+
const projection = options.projection;
70+
const sort = options.sort;
71+
const limit = options.limit !== undefined ? options.limit : defaults.findLimit;
72+
const skip = options.skip || 0;
73+
const cacheTime = options.cache !== undefined ? options.cache : defaults.cache;
74+
const maxTimeMS = options.maxTimeMS !== undefined ? options.maxTimeMS : defaults.maxTimeMS;
75+
const comment = options.comment;
76+
77+
// 3. 缓存键(包含 query, projection, sort, limit, skip)
78+
const cacheKey = cache ? `${instanceId}:${type}:${effectiveDbName}:${collection.collectionName}:findAndCount:${JSON.stringify({ query, projection, sort, limit, skip })}` : null;
79+
80+
// 4. 检查缓存
81+
if (cache && cacheTime > 0) {
82+
try {
83+
const cached = await cache.get(cacheKey);
84+
if (cached !== null) {
85+
logger?.debug?.('[findAndCount] 缓存命中', {
86+
ns: `${effectiveDbName}.${collection.collectionName}`,
87+
query: query
88+
});
89+
return cached;
90+
}
91+
} catch (cacheError) {
92+
logger?.warn?.('[findAndCount] 缓存读取失败', { error: cacheError.message });
93+
}
94+
}
95+
96+
// 5. 构建查询选项
97+
const findOptions = { maxTimeMS };
98+
if (projection) findOptions.projection = projection;
99+
if (sort) findOptions.sort = sort;
100+
if (limit !== undefined && limit !== null) findOptions.limit = limit;
101+
if (skip) findOptions.skip = skip;
102+
if (comment) findOptions.comment = comment;
103+
104+
const countOptions = { maxTimeMS };
105+
if (comment) countOptions.comment = comment;
106+
107+
// 6. 并行执行查询和计数
108+
let data, total;
109+
try {
110+
[data, total] = await Promise.all([
111+
collection.find(query, findOptions).toArray(),
112+
collection.countDocuments(query, countOptions)
113+
]);
114+
} catch (error) {
115+
throw error;
116+
}
117+
118+
// 7. 构建结果
119+
const result = { data, total };
120+
121+
// 8. 写入缓存
122+
if (cache && cacheTime > 0) {
123+
try {
124+
await cache.set(cacheKey, result, cacheTime);
125+
} catch (cacheError) {
126+
logger?.warn?.('[findAndCount] 缓存写入失败', { error: cacheError.message });
127+
}
128+
}
129+
130+
// 9. 慢查询日志
131+
const duration = Date.now() - startTime;
132+
const slowQueryMs = defaults?.slowQueryMs || 1000;
133+
134+
if (duration >= slowQueryMs) {
135+
try {
136+
const meta = {
137+
operation: 'findAndCount',
138+
durationMs: duration,
139+
iid: instanceId,
140+
type: type,
141+
db: effectiveDbName,
142+
collection: collection.collectionName,
143+
dataCount: data.length,
144+
total: total,
145+
query: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(query) : query,
146+
projection: projection,
147+
sort: sort,
148+
limit: limit,
149+
skip: skip,
150+
comment: comment
151+
};
152+
logger?.warn?.('🐌 Slow query: findAndCount', meta);
153+
emit?.('slow-query', meta);
154+
} catch (_) {
155+
// 忽略日志错误
156+
}
157+
}
158+
159+
// 10. 日志记录
160+
logger?.debug?.('[findAndCount] 查询完成', {
161+
ns: `${effectiveDbName}.${collection.collectionName}`,
162+
duration: duration,
163+
dataCount: data.length,
164+
total: total
165+
});
166+
167+
return result;
168+
};
169+
170+
return { findAndCount };
171+
}
172+
173+
module.exports = { createFindAndCountOps };
174+

‎lib/mongodb/queries/index.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ function createFindPageOps(context) {
3535
module.exports = {
3636
createFindOps: require('./find'),
3737
createFindOneOps: require('./find-one'),
38-
createFindOneByIdOps: require('./find-one-by-id').createFindOneByIdOps, // ✅ findOneById 便利方法
39-
createFindByIdsOps: require('./find-by-ids').createFindByIdsOps, // ✅ 新增:findByIds 便利方法
38+
createFindOneByIdOps: require('./find-one-by-id').createFindOneByIdOps, // findOneById 便利方法
39+
createFindByIdsOps: require('./find-by-ids').createFindByIdsOps, // findByIds 便利方法
40+
createFindAndCountOps: require('./find-and-count').createFindAndCountOps, // 新增:findAndCount 便利方法
4041
createCountOps: require('./count'),
4142
createAggregateOps: require('./aggregate'),
4243
createDistinctOps: require('./distinct'),
43-
createFindPageOps, // ✅ 新增工厂函数
44+
createFindPageOps, // 新增工厂函数
4445
// 导出原始函数和辅助函数供 bookmark 模块使用
4546
createFindPage,
4647
bookmarkKey,

0 commit comments

Comments
 (0)