以下是用于克隆本教程中所使用的应用程序源代码的命令
git clone https://github.com/redis-developer/redis-real-time-inventory-solutions
实时本地库存搜索 是一种利用跨区域或地理区域内的商店或仓库群进行高级产品搜索的能力的方法,零售商可以通过这种方法增强客户体验,提供本地化库存视图,同时从最近的商店完成订单。
对消费者附近商品的地理空间搜索有助于更快销售库存、降低库存水平,从而提高库存周转率。消费者在线查找产品,在浏览器或移动设备上下单,然后在最近的商店地点取货。这称为“在线购买,店内取货”(BOPIS)
Redis 提供跨区域或地理区域内的商店或仓库群的地理空间搜索能力,使零售商能够快速向客户展示本地的可用库存。
Redis Cloud 处理事件流,保持商店库存的实时更新。这通过本地化、准确的库存搜索增强了客户体验,同时尽可能从最近和最少的商店完成订单。
该解决方案降低了库存周转天数 (DSI),更快地销售库存,并减少库存持有量,从而在更短的时间内增加收入和利润。
它还降低了送货上门和本地商店的履约成本,增强了零售商以最低的配送和运输成本完成订单的能力。
以下是用于克隆本教程中所使用的应用程序源代码的命令
git clone https://github.com/redis-developer/redis-real-time-inventory-solutions
下载应用程序源代码后,运行以下命令将数据填充到 Redis 中
# install packages
npm install
# Seed data to Redis
npm run seed
本演示使用两个集合
productId
、name
、price
、image
和其他详细信息。下载 RedisInsight 以查看您的 Redis 数据或在工作台中操作原始 Redis 命令。
为了演示目的,我们使用美国纽约的以下区域作为商店地点。产品使用 storeId 和 quantity 映射到这些地点商店。
让我们构建以下 API 来演示使用 Redis 进行地理空间搜索
以下代码展示了 inventorySearch
API 的示例 API 请求和响应。
inventorySearch API 请求
{
"sku":1019688,
"searchRadiusInKm":500,
"userLocation": {
"latitude": 42.880230,
"longitude": -78.878738
}
}
inventorySearch API 响应
{
"data": [
{
"storeId": "02_NY_ROCHESTER",
"storeLocation": {
"longitude": -77.608849,
"latitude": 43.156578
},
"sku": 1019688,
"quantity": 38
},
{
"storeId": "05_NY_WATERTOWN",
"storeLocation": {
"longitude": -75.910759,
"latitude": 43.974785
},
"sku": 1019688,
"quantity": 31
},
{
"storeId": "10_NY_POUGHKEEPSIE",
"storeLocation": {
"longitude": -73.923912,
"latitude": 41.70829
},
"sku": 1019688,
"quantity": 45
}
],
"error": null
}
发送请求时,它会通过 API 网关到达 inventory service
。最终,它会调用一个 inventorySearch
函数,该函数如下所示。
/**
* Search Product in stores within search radius.
*
* :param _inventoryFilter: Product Id (sku), searchRadiusInKm and current userLocation
* :return: Inventory product list
*/
static async inventorySearch(_inventoryFilter: IInventoryBodyFilter): Promise<IStoresInventory[]> {
const nodeRedisClient = getNodeRedisClient();
const repository = StoresInventoryRepo.getRepository();
let retItems: IStoresInventory[] = [];
if (nodeRedisClient && repository && _inventoryFilter?.sku
&& _inventoryFilter?.userLocation?.latitude
&& _inventoryFilter?.userLocation?.longitude) {
const lat = _inventoryFilter.userLocation.latitude;
const long = _inventoryFilter.userLocation.longitude;
const radiusInKm = _inventoryFilter.searchRadiusInKm || 1000;
const queryBuilder = repository.search()
.where('sku')
.eq(_inventoryFilter.sku)
.and('quantity')
.gt(0)
.and('storeLocation')
.inRadius((circle) => {
return circle
.latitude(lat)
.longitude(long)
.radius(radiusInKm)
.kilometers
});
console.log(queryBuilder.query);
/* Sample queryBuilder query
( ( (@sku:[1019688 1019688]) (@quantity:[(0 +inf]) ) (@storeLocation:[-78.878738 42.88023 500 km]) )
*/
retItems = <IStoresInventory[]>await queryBuilder.return.all();
/* Sample command to run query directly on CLI
FT.SEARCH StoresInventory:index '( ( (@sku:[1019688 1019688]) (@quantity:[(0 +inf]) ) (@storeLocation:[-78.878738 42.88023 500 km]) )'
*/
if (!retItems.length) {
throw `Product not found with in ${radiusInKm}km range!`;
}
}
else {
throw `Input params failed !`;
}
return retItems;
}
以下代码展示了 inventorySearchWithDistance
API 的示例 API 请求和响应。
inventorySearchWithDistance API 请求
{
"sku": 1019688,
"searchRadiusInKm": 500,
"userLocation": {
"latitude": 42.88023,
"longitude": -78.878738
}
}
inventorySearchWithDistance API 响应
{
"data": [
{
"storeId": "02_NY_ROCHESTER",
"storeLocation": {
"longitude": -77.608849,
"latitude": 43.156578
},
"sku": "1019688",
"quantity": "38",
"distInKm": "107.74513"
},
{
"storeId": "05_NY_WATERTOWN",
"storeLocation": {
"longitude": -75.910759,
"latitude": 43.974785
},
"sku": "1019688",
"quantity": "31",
"distInKm": "268.86249"
},
{
"storeId": "10_NY_POUGHKEEPSIE",
"storeLocation": {
"longitude": -73.923912,
"latitude": 41.70829
},
"sku": "1019688",
"quantity": "45",
"distInKm": "427.90787"
}
],
"error": null
}
发送请求时,它会通过 API 网关到达 inventory service。最终,它会调用一个 inventorySearchWithDistance
函数,该函数如下所示。
/**
* Search Product in stores within search radius, Also sort results by distance from current user location to store.
*
* :param _inventoryFilter: Product Id (sku), searchRadiusInKm and current userLocation
* :return: Inventory product list
*/
static async inventorySearchWithDistance(_inventoryFilter: IInventoryBodyFilter): Promise<IStoresInventory[]> {
const nodeRedisClient = getNodeRedisClient();
const repository = StoresInventoryRepo.getRepository();
let retItems: IStoresInventory[] = [];
if (nodeRedisClient && repository && _inventoryFilter?.sku
&& _inventoryFilter?.userLocation?.latitude
&& _inventoryFilter?.userLocation?.longitude) {
const lat = _inventoryFilter.userLocation.latitude;
const long = _inventoryFilter.userLocation.longitude;
const radiusInKm = _inventoryFilter.searchRadiusInKm || 1000;
const queryBuilder = repository.search()
.where('sku')
.eq(_inventoryFilter.sku)
.and('quantity')
.gt(0)
.and('storeLocation')
.inRadius((circle) => {
return circle
.latitude(lat)
.longitude(long)
.radius(radiusInKm)
.kilometers
});
console.log(queryBuilder.query);
/* Sample queryBuilder query
( ( (@sku:[1019688 1019688]) (@quantity:[(0 +inf]) ) (@storeLocation:[-78.878738 42.88023 500 km]) )
*/
const indexName = `${StoresInventoryRepo.STORES_INVENTORY_KEY_PREFIX}:index`;
const aggregator = await nodeRedisClient.ft.aggregate(
indexName,
queryBuilder.query,
{
LOAD: ["@storeId", "@storeLocation", "@sku", "@quantity"],
STEPS: [{
type: AggregateSteps.APPLY,
expression: `geodistance(@storeLocation, ${long}, ${lat})/1000`,
AS: 'distInKm'
}, {
type: AggregateSteps.SORTBY,
BY: "@distInKm"
}]
});
/* Sample command to run query directly on CLI
FT.AGGREGATE StoresInventory:index '( ( (@sku:[1019688 1019688]) (@quantity:[(0 +inf]) ) (@storeLocation:[-78.878738 42.88023 500 km]) )' LOAD 4 @storeId @storeLocation @sku @quantity APPLY "geodistance(@storeLocation,-78.878738,42.88043)/1000" AS distInKm SORTBY 1 @distInKm
*/
retItems = <IStoresInventory[]>aggregator.results;
if (!retItems.length) {
throw `Product not found with in ${radiusInKm}km range!`;
}
else {
retItems = retItems.map((item) => {
if (typeof item.storeLocation == "string") {
const location = item.storeLocation.split(",");
item.storeLocation = {
longitude: Number(location[0]),
latitude: Number(location[1]),
}
}
return item;
})
}
}
else {
throw `Input params failed !`;
}
return retItems;
}
希望本教程能帮助您直观地了解如何在不同区域商店之间使用 Redis 进行实时本地库存搜索。有关此主题的更多资源,请查看下面的链接。
使用 Redis 的实时库存
通用