如何从Rest API过滤Mongoose



因此,我正在使用Express创建一个API。在这个API上,我想获得使用Mongoose的MongoDB客户列表。我有以下路线(对于我的问题,忽略我的寻呼和限制(。

routes.get("/", async (req, res) => {
const page = req.query.page;
let limit = req.query.limit;
const match = GetCustomerFilters(req);
if (limit && limit > 100) {
limit = 25;
}
const results = await GetCustomers({ page, limit, match });
res.status(200).json({
status: "ok",
data: {
currentPage: results.currentPage,
totalPages: results.totalPages,
limit: results.limit,
count: results.data.length,
total: results.count,
results: results.data,
},
});
});

为了完成这一点,我将首先向您展示GetCustomerFilters函数的样子,它基于路由中传入的req变量创建过滤查询。请注意,这些都在不同的文件中,但我会把它们都放在这里让你看看。

const GetCustomerFilters = (req) => {
const match = {};
const filterActive = req.query.active;
const filterCreated = req.query.created;
const filterCreated_lt = req.query.created_lt;
const filterCreated_gt = req.query.created_gt;
const filterCreated_lte = req.query.created_lte;
const filterCreated_gte = req.query.created_gte;
if (filterActive != undefined) {
match.active = filterActive.toLowerCase() === "true";
}
if (filterCreated) {
match.createdAt = Date.parse(filterCreated);
} else {
let matchCreatedAt = {};
if (filterCreated_lt) {
matchCreatedAt = { ...matchCreatedAt, $lt: Date.parse(filterCreated_lt) };
}
if (filterCreated_gt) {
matchCreatedAt = { ...matchCreatedAt, $gt: Date.parse(filterCreated_gt) };
}
if (filterCreated_lte) {
matchCreatedAt = {
...matchCreatedAt,
$lte: Date.parse(filterCreated_lte),
};
}
if (filterCreated_gte) {
matchCreatedAt = {
...matchCreatedAt,
$gte: Date.parse(filterCreated_gte),
};
}
if (
!(
Object.keys(matchCreatedAt).length === 0 &&
matchCreatedAt.constructor === Object
)
) {
match.createdAt = matchCreatedAt;
}
}
return match;
};

最后,为了完成第一个代码块,我有了GetCustomers函数。

const GetCustomers = async ({ page = 1, limit = 25, match }) => {
return new Promise(async (resolve, reject) => {
page = Number.parseInt(page) - 1;
limit = Number.parseInt(limit);
const total = await CustomerModel.countDocuments();
const totalPages = Math.ceil(total / limit);
return CustomerModel.find({ ...match })
.limit(limit)
.skip(limit * page)
.then((results) => {
resolve({
currentPage: page + 1,
totalPages,
limit,
count: total,
data: results,
});
})
.catch((err) => {
return reject(err);
});
});
};

我的问题是,这能变得更好吗。我关心的第一个问题(您可以在第二块代码中看到(是,在创建Customer时,我有不同的过滤方式的所有代码。它可以精确匹配,在某个日期之前、某个日期之后、某个时间之前或等于某个时间,以及某个时间之后或等于某一时间。我的第一个想法是将其抽象为一个单独的函数,其中相同的函数用于任何日期,我会传入数据库字段和实际的查询参数。会有不同/更好的方法吗?也许使用下面的功能

const FilterDate = (fieldExact, fieldLT, fieldGT, fieldLTE, fieldGTE) => {
let results = null;
if (fieldExact) {
results = Date.parse(fieldExact);
} else {
results = {};
if (fieldLT) {
results  = { ...results, $lt: Date.parse(fieldLT) };
}
if (fieldGT) {
results = { ...results, $gt: Date.parse(fieldGT) };
}
if (fieldLTE) {
results = {
...results,
$lte: Date.parse(fieldLTE),
};
}
if (fieldGTE) {
results = {
...results,
$gte: Date.parse(fieldGTE),
};
}
}

return results;
}

然后使用将其添加到匹配中

let createdAtFilter = FilterDate(req.query.created, req.query.created_lt,
req.query.created_gt, req.query.created_lte, req.query.created_gte);
if (
!(Object.keys(createdAtFilter).length === 0 && createdAtFilter.constructor === Object)
) {
match.createdAt = createdAtFilter;
}

这样,如果我有一个updatedAt字段,我就不必再次执行所有这些检查,并且可以重用代码。

我可以看到一些改进的空间。首先,假设来自调用方的查询不可更改,考虑将调用方的API比较运算符直接关联到Mongo。这样,我们就可以将过滤代码的创建部分缩减为一个漂亮的、声明性的reduce(),如下所示:

const ops = {
created: '$eq',
created_lt: '$lt',
created_gt: '$gt',
created_lte: '$lte',
created_gte: '$gte'
};
function GetCustomerFilters(req) {
const query = req.query;
const comparators = Object.entries(query).reduce((acc, [key, value]) => {
if (ops[key]) acc[ops[key]] = Date.parse(value);
return acc;
}, {});
let filters = { createdAt: comparators };
filters.active = req.query.active === 'true';
return filters; 
}
const exampleRequest = {
query: {
active: 'true',
created_gte: '07/04/2020',
created_lt: '07/09/2020'
}
};
console.log(GetCustomerFilters(exampleRequest));

应该对代码进行的另一个改进是在promise构造函数中包装find()find()返回一个promise,通过在调用链上使用.then()可以清楚地表明这一点,因此不需要将该promise封装在另一个promise中。换句话说,像这样改进它:

const GetCustomers = async ({ page = 1, limit = 25, match }) => {
page = Number.parseInt(page) - 1;
limit = Number.parseInt(limit);
const total = await CustomerModel.countDocuments();
const totalPages = Math.ceil(total / limit);
return CustomerModel.find({ ...match })
.limit(limit)
.skip(limit * page)
.then(results => {
return {
currentPage: page + 1,
totalPages,
limit,
count: total,
data: results,
};
})
};

我认为最好的方法是将日期范围分离,这意味着您应该为查询创建函数提供两个参数,一个用于被查询的日期(在本例中为createdAt(,另一个指定要搜索的范围,例如equal、gt、lt,这第二个参数将可能通过向用户提供预定义的选项列表来从前端提供
例如:

function createFilter(date, range) {//range here will be a string one of ['gt','gte','lt','lte'] or nothing - which means exact match.  

date = Date.parse(date);

let match =  range ? {['$'+range]:date} : date;
return {createdAt: match}
};

这样,如果将来需要在不同的字段上使用相同的查询模式,只需添加另一个参数field_name并将返回的查询对象更新为{[field_name]:match}

最新更新