学习

如何使用 NodeJS 和 Redis 构建一个购物车应用

Ajeet Raina
作者
Ajeet Raina, Redis 前开发者增长经理

很难想象一个没有购物车的在线商店。几乎每个在线商店都必须具备购物车功能,才能向客户销售产品。为了构建可扩展的电子商务平台,你需要一个强大的框架和一个简单的存储系统。有时,许多开发者专注于改进电子商务平台的前端性能来解决这些问题。然而,真正的瓶颈仍然是后端加载时间慢。缓慢的后端加载时间会对你的搜索引擎排名产生严重影响。一个好的经验法则是,后端加载时间不应超过总加载时间的 20%。后端加载时间的良好目标是 200 毫秒或更短。在本教程中,你将看到如何使用 Node.js、Vue.js、Express 和 Redis 构建一个购物车应用。

目录#

  • 你将构建什么?
  • 你需要什么?
  • 快速入门
  • 设置后端 (Node.js Express)
  • 设置前端 (Vue.js)
  • 运行应用
  • 结论

你将构建什么?#

本教程将向你展示如何通过使用 Node.js 创建一个基本的电子商务购物车应用来利用 Redis 的强大功能。通常,购物车数据以 cookie 的形式存储在客户端。Cookie 是存储在网络用户的浏览器目录或数据文件夹中的小型文本文件。这样做的好处是你无需将这些临时数据存储在数据库中。然而,这需要你在每次 Web 请求时发送 cookie,这在 cookie 较大时可能会降低请求速度。将购物车数据存储在 Redis 中是一个好主意,因为你可以随时非常快速地检索商品,并在需要时持久化这些数据。

你需要什么?#

  • Redis Stack
  • Express 4 backend
  • Node 15.5.0 (at least v12.9.0+)
  • NPM 7.3.0 (at least v6.14.8+)
  • Docker 19.03.X (可选)
  • Docker Compose (可选)

使用 Node.js 构建电子商务应用更有意义,因为它异步的特性(能够同时处理多个并发用户)确保了前端和后端加载时间的平衡。Node.js 帮助开发者最大限度地利用事件循环和回调进行 I/O 操作。Node.js 运行的是单线程、非阻塞、异步编程,这非常节省内存。

为了创建一个购物车,我们需要一个简单的存储系统,在那里我们可以收集商品和购物车的总计。Node.js 为我们提供了 express-session 包,它是 ExpressJS 的中间件。我们将使用 express-session 中间件来管理 Node.js 中的会话。会话存储在 Express 服务器本身。

默认的服务器端会话存储 MemoryStore 特意设计为不适用于生产环境。它在大多数情况下会发生内存泄漏,无法扩展超过单个进程,并且仅用于调试和开发。要管理多个用户的多个会话,我们必须创建一个全局映射并将每个会话对象放入其中。NodeJs 中的全局变量会消耗内存,并且在生产级别的项目中可能成为严重的安全漏洞。这可以通过使用外部会话存储来解决。我们必须将每个会话存储在存储中,以便每个会话仅属于单个用户。一种流行的会话存储是使用 Redis 构建的。

我们将首先为我们的应用设置后端。让我们为应用创建一个新目录并初始化一个新的 Node.js 应用。打开你的终端并输入以下内容:

快速入门#

克隆仓库

$ git clone https://github.com/redis-developer/basic-redis-shopping-chart-nodejs

运行 Redis Stack#

你可以使用下面的 docker compose 文件来运行 Redis Stack 服务器

version: '3'

services:
    redis:
        image: redis/redis-stack:latest
        container_name: redis.redisshoppingcart.docker
        restart: unless-stopped
        environment:
            REDIS_PASSWORD: ${REDIS_PASSWORD}
        ports:
            - 127.0.0.1:${REDIS_PORT}:6379
        networks:
            - global
networks:
    global:
        external: true

我假设你的本地环境已安装并运行 Docker 和 Docker Compose。执行下面的 compose CLI 启动 Redis 服务器

$ docker network create global
$ docker-compose up -d --build

命令 docker-compose ps 显示正在运行的 Redis 服务列表:

$ docker-compose ps
Name                           Command               State          Ports

redis.redisshoppingcart.docker   docker-entrypoint.sh redis ...   Up      127.0.0.1:55000->6379/tcp

设置后端服务器#

Node.js 是一个运行时环境,允许软件开发者使用 JavaScript 启动 Web 应用的前端和后端。为了节省你的时间,目录 /server/src 已为你创建。我们将在此处通过添加以下子目录来创建我们的模块 -

  • routes
  • controller
  • middleware
  • services

路由将支持的请求(以及请求 URL 中编码的任何信息)转发给适当的控制器函数,而控制器函数从模型中获取请求的数据,创建显示数据的 HTML 页面,并将其返回给用户在浏览器中查看。服务层包含你的实际业务逻辑。中间件函数是那些可以访问请求对象 (req)、响应对象 (res) 以及应用请求-响应周期中的下一个中间件函数的函数。

项目目录结构#

% tree
.
├── controllers
│   ├── Cart
│   │   ├── DeleteItemController.js
│   │   ├── EmptyController.js
│   │   ├── IndexController.js
│   │   └── UpdateController.js
│   └── Product
│       ├── IndexController.js
│       └── ResetController.js
├── index.js
├── middleware
│   └── checkSession.js
├── products.json
├── routes
│   ├── cart.js
│   ├── index.js
│   └── products.js
└── services
    └── RedisClient.js

6 directories, 13 files

首先,让我们通过下面展示的 index.js 文件初始化应用服务器

// server/src/index.js


const express = require('express');
const redis = require('redis');
const rejson = require('redis-rejson');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const path = require('path');
const bodyParser = require('body-parser');
const cors = require('cors');
const RedisClient = require('./services/RedisClient');

rejson(redis);

require('dotenv').config();

const { REDIS_ENDPOINT_URI, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, PORT } = process.env;

const app = express();

app.use(
    cors({
        origin(origin, callback) {
            callback(null, true);
        },
        credentials: true
    })
);

const redisEndpointUri = REDIS_ENDPOINT_URI
    ? REDIS_ENDPOINT_URI.replace(/^(redis\:\/\/)/, '')
    : `${REDIS_HOST}:${REDIS_PORT}`;

const redisClient = redis.createClient(`redis://${redisEndpointUri}`, {
    password: REDIS_PASSWORD
});

const redisClientService = new RedisClient(redisClient);

app.set('redisClientService', redisClientService);

app.use(
    session({
        store: new RedisStore({ client: redisClient }),
        secret: 'someSecret',
        resave: false,
        saveUninitialized: false,
        rolling: true,
        cookie: {
            maxAge: 3600 * 1000 * 3
        }
    })
);

app.use(bodyParser.json());

app.use('/', express.static(path.join(__dirname, '../../client-dist')));

const router = require('./routes')(app);

app.use('/api', router);

const port = PORT || 3000;

app.listen(port, () => {
    console.log(`App listening on port ${port}`);
});

你会看到这个 index.js 的职责仅仅是设置服务器。它初始化所有中间件,设置视图引擎等。最后要做的是通过将路由设置的职责委托给 routes 文件夹内的 index.js 来设置路由。

如上所示,app.use、app.set 和 app.listen 是端点,出于本次演示的目的,我们需要能够向购物车添加和获取商品(保持简单)。我们需要定义获取所有商品、获取单个商品详情、移除商品和创建商品的基本路由。

路由#

routes 目录只负责定义我们的路由。在该文件夹中的 index.js 文件中,你会看到它的职责是设置我们的顶级路由,并将它们的职责委托给各自的路由文件。每个相应的路由文件将进一步定义各自的附加子路由和控制器动作。

Web 服务器骨架已经有一个 ./routes 文件夹,其中包含 index、products 和 cart 的路由。(如 https://github.com/redis-developer/basic-redis-shopping-chart-nodejs/tree/main/server/src/routes 中所示)

// routes/index.js

const fs = require('fs');
const express = require('express');
const router = express.Router();

module.exports = app => {
    fs.readdirSync(__dirname).forEach(function (route) {
        route = route.split('.')[0];

        if (route === 'index') {
            return;
        }

        router.use(`/${route}`, require(`./${route}`)(app));
    });

    return router;
};

路由是 Express 代码的一部分,它将 HTTP 动词(GET、POST、PUT、DELETE 等)、URL 路径/模式以及处理该模式的函数关联起来。创建路由有几种方法。对于本次演示应用,我们将使用 express.Router 中间件,因为它允许我们将网站特定部分的路由处理程序组合在一起,并使用通用的路由前缀来访问它们。该模块需要 Express,然后使用它创建一个 Router 对象。所有路由都在路由器上设置,然后导出。路由是使用路由器对象的 .get() 或 .post() 方法定义的。所有路径都使用字符串定义(我们不使用字符串模式或正则表达式)。对某些特定资源(例如,图书)进行操作的路由使用路径参数从 URL 中获取对象 ID。处理函数全部从我们在上一节创建的控制器模块中导入。

控制器#

控制器负责调用适当的动作。如果控制器的职责是渲染视图,它将从 app/views 目录中渲染相应的视图。

// controller/Product/IndexController.js


const { products } = require('../../products.json');

class ProductIndexController {
    constructor(redisClientService) {
        this.redisClientService = redisClientService;
    }

    async index(req, res) {
        const productKeys = await this.redisClientService.scan('product:*');
        const productList = [];

        if (productKeys.length) {
            for (const key of productKeys) {
                const product = await this.redisClientService.jsonGet(key);

                productList.push(JSON.parse(product));
            }

            return res.send(productList);
        }

        for (const product of products) {
            const { id } = product;

            await this.redisClientService.jsonSet(`product:${id}`, '.', JSON.stringify(product));

            productList.push(product);
        }

        return res.send(productList);
    }
}

module.exports = ProductIndexController;

服务#

服务层包含你的实际业务逻辑。服务层执行应用逻辑,并将 CRUD 操作委托给数据库/持久存储(在本例中是 Redis)。让我们看看每种情况,并尝试理解数据是如何存储、修改和访问的

数据如何存储:#

商品数据存储在外部 JSON 文件中。在第一次请求后,这些数据会以 JSON 数据类型存储在 Redis 中,如下所示:

JSON.SET product:{productId} . '{ "id": "productId", "name": "Product Name", "price": "375.00", "stock": 10 }'.

示例:#

购物车数据存储在哈希中,如下所示:

HSET cart:{cartId} product:{productId} {productQuantity},

其中 cartId 是随机生成的值,存储在用户会话中。请注意,Redis 的哈希管理命令 HSET 存储了两个键 - cart 和 product - 如以下示例所示。

示例:#

HSET cart:77f7fc881edc2f558e683a230eac217d product:e182115a-63d2-42ce-8fe0-5f696ecdfba6 1

数据如何修改:#

商品数据修改如下:

JSON.SET product:{productId} . '{ "id": "productId", "name": "Product Name", "price": "375.00", "stock": {newStock} }'.

示例:#

JSON.SET product:e182115a-63d2-42ce-8fe0-5f696ecdfba6 . '{ "id": "e182115a-63d2-42ce-8fe0-5f696ecdfba6", "name": "Brilliant Watch", "price": "250.00", "stock": 1 }'

购物车数据修改如下:

HSET cart:{cartId} product:{productId} {newProductQuantity} or HINCRBY cart:{cartId} product:{productId} {incrementBy}.

示例:#

HSET cart:77f7fc881edc2f558e683a230eac217d product:e182115a-63d2-42ce-8fe0-5f696ecdfba6 2

HINCRBY cart:77f7fc881edc2f558e683a230eac217d product:e182115a-63d2-42ce-8fe0-5f696ecdfba6 1

HINCRBY cart:77f7fc881edc2f558e683a230eac217d product:e182115a-63d2-42ce-8fe0-5f696ecdfba6 -1

商品可以从购物车中移除,如下所示:

HDEL cart:{cartId} product:{productId}

示例:#

HDEL cart:77f7fc881edc2f558e683a230eac217d product:e182115a-63d2-42ce-8fe0-5f696ecdfba6

购物车可以使用以下命令清空:

HGETALL cart:{cartId} and then HDEL cart:{cartId} {productKey} in loop.

示例:#

HGETALL cart:77f7fc881edc2f558e683a230eac217d => product:e182115a-63d2-42ce-8fe0-5f696ecdfba6, product:f9a6d214-1c38-47ab-a61c-c99a59438b12, product:1f1321bb-0542-45d0-9601-2a3d007d5842 => HDEL cart:77f7fc881edc2f558e683a230eac217d product:e182115a-63d2-42ce-8fe0-5f696ecdfba6, HDEL cart:77f7fc881edc2f558e683a230eac217d product:f9a6d214-1c38-47ab-a61c-c99a59438b12, HDEL cart:77f7fc881edc2f558e683a230eac217d product:1f1321bb-0542-45d0-9601-2a3d007d5842

当请求重置数据时,所有购物车都可以被删除,如下所示:

 SCAN {cursor} MATCH cart:* and then DEL cart:{cartId} in loop.

示例:#

 SCAN {cursor} MATCH cart:* => cart:77f7fc881edc2f558e683a230eac217d, cart:217dedc2f558e683a230eac77f7fc881, cart:1ede77f558683a230eac7fc88217dc2f => DEL cart:77f7fc881edc2f558e683a230eac217d, DEL cart:217dedc2f558e683a230eac77f7fc881, DEL cart:1ede77f558683a230eac7fc88217dc2f

数据如何访问:#

商品:使用 SCAN {cursor} MATCH product:* 获取所有商品键,然后使用 JSON.GET {productKey}

示例:#

SCAN {cursor} MATCH product:* => product:e182115a-63d2-42ce-8fe0-5f696ecdfba6, product:f9a6d214-1c38-47ab-a61c-c99a59438b12, product:1f1321bb-0542-45d0-9601-2a3d007d5842
=> JSON.GET product:e182115a-63d2-42ce-8fe0-5f696ecdfba6, JSON.GET product:f9a6d214-1c38-47ab-a61c-c99a59438b1, JSON.GET product:1f1321bb-0542-45d0-9601-2a3d007d5842

购物车:使用 HGETALL cart:{cartId} 获取商品数量,然后使用 JSON.GET product:{productId} 在循环中获取商品数据。

示例:#

HGETALL cart:77f7fc881edc2f558e683a230eac217d => product:e182115a-63d2-42ce-8fe0-5f696ecdfba6 (quantity: 1), product:f9a6d214-1c38-47ab-a61c-c99a59438b12 (quantity: 0), product:1f1321bb-0542-45d0-9601-2a3d007d5842 (quantity: 2) => JSON.GET product:e182115a-63d2-42ce-8fe0-5f696ecdfba6, JSON.GET product:f9a6d214-1c38-47ab-a61c-c99a59438b12, JSON.GET product:1f1321bb-0542-45d0-9601-2a3d007d5842

HGETALL 返回哈希数据类型中的键和对应值的数组。使用你喜欢的编辑器打开 RedisClient.js 文件,如下所示:

// services/RedisClient.js

const { promisify } = require('util');

class RedisClient {
    constructor(redisClient) {
        ['json_get', 'json_set', 'hgetall', 'hset', 'hget', 'hdel', 'hincrby', 'del', 'scan'].forEach(
            method => (redisClient[method] = promisify(redisClient[method]))
        );
        this.redis = redisClient;
    }

    async scan(pattern) {
        let matchingKeysCount = 0;
        let keys = [];

        const recursiveScan = async (cursor = '0') => {
            const [newCursor, matchingKeys] = await this.redis.scan(cursor, 'MATCH', pattern);
            cursor = newCursor;

            matchingKeysCount += matchingKeys.length;
            keys = keys.concat(matchingKeys);

            if (cursor === '0') {
                return keys;
            } else {
                return await recursiveScan(cursor);
            }
        };

        return await recursiveScan();
    }

    jsonGet(key) {
        return this.redis.json_get(key);
    }

    jsonSet(key, path, json) {
        return this.redis.json_set(key, path, json);
    }

    hgetall(key) {
        return this.redis.hgetall(key);
    }

    hset(hash, key, value) {
        return this.redis.hset(hash, key, value);
    }

    hget(hash, key) {
        return this.redis.hget(hash, key);
    }

    hdel(hash, key) {
        return this.redis.hdel(hash, key);
    }

    hincrby(hash, key, incr) {
        return this.redis.hincrby(hash, key, incr);
    }

    del(key) {
        return this.redis.del(key);
    }
}

module.exports = RedisClient;

整个过程是如何工作的?#

流程非常简单。一旦请求发送到此购物车应用的某个端点,例如 http://localhost:8081/。它首先命中该端点的路由器,如果是公共端点,则会转到处理该请求的控制器。打个比方,控制器就像一个经理,而服务层就像工人。控制器管理传入的 HTTP 请求,而服务层从经理那里接收所需的请求数据以执行任务。

接下来,我们在名为 cart.js 的模块中为购物车创建路由。代码首先导入 Express 应用对象,使用它获取一个 Router 对象,然后使用 get() 方法向其中添加几个路由。最后,模块返回 Router 对象。

首先,让我们在 controllers/Product/IndexController.js 文件中定义商品模型(https://github.com/redis-developer/basic-redis-shopping-chart-nodejs/tree/main/server/src/controllers/Product

我们的商品模型将尽可能基础,因为它只包含商品名称、价格和图片。

{
    "products": [
        {
            "id": "e182115a-63d2-42ce-8fe0-5f696ecdfba6",
            "name": "Brilliant Watch",
            "price": "250.00",
            "stock": 2
        },
        {
            "id": "f9a6d214-1c38-47ab-a61c-c99a59438b12",
            "name": "Old fashion cellphone",
            "price": "24.00",
            "stock": 2
        },
        {
            "id": "1f1321bb-0542-45d0-9601-2a3d007d5842",
            "name": "Modern iPhone",
            "price": "1000.00",
            "stock": 2
        },
        {
            "id": "f5384efc-eadb-4d7b-a131-36516269c218",
            "name": "Beautiful Sunglasses",
            "price": "12.00",
            "stock": 2
        },
        {
            "id": "6d6ca89d-fbc2-4fc2-93d0-6ee46ae97345",
            "name": "Stylish Cup",
            "price": "8.00",
            "stock": 2
        },
        {
            "id": "efe0c7a3-9835-4dfb-87e1-575b7d06701a",
            "name": "Herb caps",
            "price": "12.00",
            "stock": 2
        },
        {
            "id": "x341115a-63d2-42ce-8fe0-5f696ecdfca6",
            "name": "Audiophile Headphones",
            "price": "550.00",
            "stock": 2
        },
        {
            "id": "42860491-9f15-43d4-adeb-0db2cc99174a",
            "name": "Digital Camera",
            "price": "225.00",
            "stock": 2
        },
        {
            "id": "63a3c635-4505-4588-8457-ed04fbb76511",
            "name": "Empty Bluray Disc",
            "price": "5.00",
            "stock": 2
        },
        {
            "id": "97a19842-db31-4537-9241-5053d7c96239",
            "name": "256BG Pendrive",
            "price": "60.00",
            "stock": 2
        }
    ]
}

测试服务器#

将 .env.example 复制到 .env 文件,并设置如下所示的环境变量:

REDIS_PORT=6379
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=demo

COMPOSE_PROJECT_NAME=redis-shopping-cart
信息

如果您使用的是 Redis Cloud 而不是 localhost,则需要在 REDIS_HOST 下输入数据库端点(不含端口),而 REDIS_PORT 和 REDIS_PASSWORD 等其余条目则非常明显

安装依赖项#

$ npm install

测试路由#

添加完成后,您可以在终端中输入 npm install 运行应用程序。运行此命令后,它将返回 Application is running on 3000。

$ npm run dev
$ npm run dev

> [email protected] dev
> nodemon src/index.js

[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/index.js`
App listening on port 3000

使用 Vue.js 设置前端 Web 客户端#

后端应用程序现已运行,接下来我们开始开发其前端。我们将利用 Vue.js——一个强大而简单的 JavaScript 框架来构建我们的前端 Web 客户端。与其他现代框架相比,它的入门门槛最低,同时为高性能 Web 应用程序提供了所有必需的功能。

.
├── README.md
├── babel.config.js
├── node_modules
├── package-lock.json
├── package.json
├── public
├── src
└── vue.config.js

根目录下的文件(babel.config.js、package.json、node_modules)用于配置项目。至少目前而言,最有趣的部分位于 src 目录中(目录结构如下所示)

main.js 文件是应用程序的主要 JavaScript 文件,它将加载所有公共元素并调用 App.vue 主屏幕。App.vue 是一个文件,其中包含特定页面或模板的 HTML、CSS 和 JavaScript。作为应用程序的入口点,此部分默认由所有屏幕共享,因此是在此文件中编写通知客户端代码的好地方。public/index.html 是静态入口点,DOM 将从此处加载。

目录结构:#

% tree
.
├── App.vue
├── assets
│   ├── RedisLabs_Illustration.svg
│   └── products
│       ├── 1f1321bb-0542-45d0-9601-2a3d007d5842.jpg
│       ├── 42860491-9f15-43d4-adeb-0db2cc99174a.jpg
│       ├── 63a3c635-4505-4588-8457-ed04fbb76511.jpg
│       ├── 6d6ca89d-fbc2-4fc2-93d0-6ee46ae97345.jpg
│       ├── 97a19842-db31-4537-9241-5053d7c96239.jpg
│       ├── e182115a-63d2-42ce-8fe0-5f696ecdfba6.jpg
│       ├── efe0c7a3-9835-4dfb-87e1-575b7d06701a.jpg
│       ├── f5384efc-eadb-4d7b-a131-36516269c218.jpg
│       ├── f9a6d214-1c38-47ab-a61c-c99a59438b12.jpg
│       └── x341115a-63d2-42ce-8fe0-5f696ecdfca6.jpg
├── components
│   ├── Cart.vue
│   ├── CartItem.vue
│   ├── CartList.vue
│   ├── Info.vue
│   ├── Product.vue
│   ├── ProductList.vue
│   └── ResetDataBtn.vue
├── config
│   └── index.js
├── main.js
├── plugins
│   ├── axios.js
│   └── vuetify.js
├── store
│   ├── index.js
│   └── modules
│       ├── cart.js
│       └── products.js
└── styles
    └── styles.scss

8 directories, 27 files

在 client 目录下的 src 子目录中,打开 App.vue 文件。您将看到以下内容

<template>
    <v-app>
        <v-container>
            <div class="my-8 d-flex align-center">
                <div class="pa-4 rounded-lg red darken-1">
                    <v-icon color="white" size="45">mdi-cart-plus</v-icon>
                </div>
                <h1 class="ml-6 font-weight-regular">Shopping Cart demo</h1>
            </div>
        </v-container>

        <v-container>
            <v-row>
                <v-col cols="12" sm="7" md="8">
                    <info />
                    <product-list :products="products" />
                </v-col>
                <v-col cols="12" sm="5" md="4" class="d-flex flex-column">
                    <cart />
                    <reset-data-btn class="mt-6" />
                </v-col>
            </v-row>

            <v-footer class="mt-12 pa-0">
                © Copyright 2021 | All Rights Reserved Redis
            </v-footer>
        </v-container>
    </v-app>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';
import Cart from '@/components/Cart';
import ProductList from '@/components/ProductList';
import ResetDataBtn from '@/components/ResetDataBtn.vue';
import Info from '@/components/Info';

export default {
    name: 'App',

    components: {
        ProductList,
        Cart,
        ResetDataBtn,
        Info
    },

    computed: {
        ...mapGetters({
            products: 'products/getProducts'
        })
    },

    async created() {
        await this.fetchProducts();
    },

    methods: {
        ...mapActions({
            fetchProducts: 'products/fetch'
        })
    }
};
</script>

这是客户端代码。API 在这里返回,其中包括适用于地图使用的图标链接。如果您按流程操作,您会看到地图标记直接使用包含的 URL 加载这些图标。

运行/测试 Web 客户端#

$ cd client
$ npm run serve

> [email protected] serve
> vue-cli-service serve

 INFO  Starting development server...
98% after emitting CopyPlugin

 DONE  Compiled successfully in 7733ms                                                                                                                              7:15:56 AM


  App running at:
  - Local:   http://localhost:8081/
  - Network: http://192.168.43.81:8081/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

让我们点击第一个项目“256GB Pendrive”,尝试结账该产品。将其添加到购物车后,您将看到使用 redis-cli monitor 命令生成的以下输出

1613320256.801562 [0 172.22.0.1:64420] "json.get" "product:97a19842-db31-4537-9241-5053d7c96239"
1613320256.803062 [0 172.22.0.1:64420] "hget"
...
1613320256.805950 [0 172.22.0.1:64420] "json.set" "product:97a19842-db31-4537-9241-5053d7c96239" "." "{\"id\":\"97a19842-db31-4537-9241-5053d7c96239\",\"name\":\"256BG Pendrive\",\"price\":\"60.00\",\"stock\":1}"
1613320256.807792 [0 172.22.0.1:64420] "set" "sess:Ii9njXZd6zeUViL3tKJimN5zU7Samfze"
...
1613320256.823055 [0 172.22.0.1:64420] "scan" "0" "MATCH" "product:*"
...
1613320263.232527 [0 172.22.0.1:64420] "hgetall" "cart:bdee1606395f69985e8f8e01d3ada8c4"
1613320263.233752 [0 172.22.0.1:64420] "set" "sess:gXk5K9bobvrR790-HFEoi3bQ2kP9YmjV" "{\"cookie\":{\"originalMaxAge\":10800000,\"expires\":\"2021-02-14T19:31:03.233Z\",\"httpOnly\":true,\"path\":\"/\"},\"cartId\":\"bdee1606395f69985e8f8e01d3ada8c4\"}" "EX" "10800"
1613320263.240797 [0 172.22.0.1:64420] "scan" "0" "MATCH" "product:*"
1613320263.241908 [0 172.22.0.1:64420] "scan" "22" "MATCH" "product:*"
"{\"cookie\":{\"originalMaxAge\":10800000,\"expires\":\"2021-02-14T19:31:03.254Z\",\"httpOnly\":true,\"path\":\"/\"},\"cartId\":\"4bc231293c5345370f8fab83aff52cf3\"}" "EX" "10800"

结论#

将购物车数据存储在 Redis 中是一个好主意,因为它可以让您随时非常快速地检索数据,并在需要时持久化这些数据。与将整个购物车数据存储在会话中(这会导致会话臃肿且操作相对较慢)的 Cookie 相比,将购物车数据存储在 Redis 中可以加快购物车的读写性能,从而改善用户体验。

参考资料#