Skip to content

8. 示例 [nuxt-05]:利用会话 Cookie 保持存储状态

目标:希望存储 [Vuex] 不会在每次向服务器发送请求时被重置。为此,我们将使用一个会话 Cookie:

  • 存储将由服务器初始化,并由服务器将其放入会话cookie中;
  • 客户端浏览器将接收该会话cookie,并在每次向服务器发送新请求时自动将其发送出去;
  • 服务器随后可读取该会话cookie,并使用其中包含的存储数据进行处理,该存储数据由客户端实时更新;

8.1. Présentation

项目 [nuxt-05] 最初是通过复制项目 [nuxt-04] 获得的:

Image

我们将看到,只有文件 [store / index.js] 会发生变化。

要在 [nuxt] 中使用 Cookie,我们将使用模块 [cookie-universal-nuxt],该模块随 [yarn] 一起安装在终端 VSCode 中:

Image

  • 在 [4] 中,输入命令 [yarn add cookie-universal-nuxt];

这样,项目 [dvp] 的文件 [package.json] 中便新增了一个模块:


...
},
  "dependencies": {
    "@nuxtjs/axios": "^5.3.6",
    "bootstrap": "^4.1.3",
    "bootstrap-vue": "^2.0.0",
    "cookie-universal-nuxt": "^2.0.19",
    "nuxt": "^2.0.0"
},

8.2. 配置文件 [nuxt.config.js]

为了使 [nuxt] 能够使用 [cookie-universal-nuxt] 的 Cookie,需要在配置文件 [nuxt.config.js] 中声明该模块:


...
],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // 文档:https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // 文档:https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // https://www.npmjs.com/package/cookie-universal-nuxt
    'cookie-universal-nuxt'
  ],
...

  • 第 12 行,将模块 [cookie-universal-nuxt] 添加到 [6] 的模块数组中(该数组属于 [nuxt]);

最终文件 [nuxt.config.js] 如下所示:


export default {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: 'Introduction à [nuxt.js]',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'ssr routing loading asyncdata middleware plugins store'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: '#fff' },
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // 文档:https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // 文档:https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // 文档:https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // https://www.npmjs.com/package/cookie-universal-nuxt
    'cookie-universal-nuxt'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {}
  },
  // 源代码目录
  srcDir: 'nuxt-05',
  // 路由器
  router: {
    // 应用程序的 URL 根目录
    base: '/nuxt-05/'
  },
  // 服务器
  server: {
    // 服务端口,默认 3000
    port: 81,
    // 监听的网络地址,默认 localhost:127.0.0.1
    // 0.0.0.0 = 该机器的所有网络地址
    host: 'localhost'
  },
  // 环境
  env: {
    maxAge: 60 * 5
  }
}
  • 第 79 行:已向文件中添加了键 [env]。该键为保留字。在此对象中声明的元素可通过应用程序元素中的 [context.env] 对象访问;
  • 第 80 行:[maxAge] 属性将作为会话 Cookie 的最大生命周期,该时长从 Cookie 最后一次初始化时开始计算。该时长以秒为单位。此处设置的生命周期为 5 分钟;

8.3. 存储持久化的原理

客户端与服务器之间交换的 Cookie 在双方(客户端和服务器)均可通过以下路径访问:

  • [context.app.$cookies](即对象 [context] 可用之处,也就是几乎所有地方);
  • [this.$cookies](位于某个视图内部);

使用表达式 [...$cookies.get(‘nom_du_cookie’)] 可获取特定 Cookie。使用表达式 [...$cookies.set(‘nom_du_cookie’, valeur_du_cookie)] 可设置 Cookie 的值。

存储持久化 Cookie 的原理如下:

  • 当服务器在函数 [nuxtServerInit] 中初始化存储时,存储的状态将保存在名为“session”的 Cookie 中;
  • 该“session”Cookie将作为服务器响应HTTP的一部分返回。众所周知,浏览器会将服务器发送的Cookie回传给服务器,且在每次向服务器发起新请求时都会如此。同样,服务器会在发送给客户端的页面中包含存储区;
  • 在浏览器中,客户端应用程序会获取服务器发送的存储数据,并据此执行后续操作。我们将确保每次修改存储数据时,其新状态都会被存储在浏览器记录的“session”Cookie中;
  • 如果用户强制向服务器发起请求,客户端浏览器将自动返回服务器先前发送的所有cookie,特别是名为“session”的cookie;
  • 当服务器在响应此请求后重新初始化数据存储时,它将获取名为“session”的Cookie,并使用其中的值来初始化数据存储的状态;
  • 因此,客户端与服务器之间的存储状态将保持连续性;

8.4. 存储的初始化

存储器在文件 [store / index.js] 中实现:


/* eslint-disable no-console */
export const state = () => ({
  // 计数器
  counter: 0
})

export const mutations = {
  // 计数器递增一个值 [inc]
  increment(state, inc) {
    state.counter += inc
  },
  // 状态替换
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  }
}

export const actions = {
  async nuxtServerInit(store, context) {
    // 谁在执行这段代码?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // 等待一个 Promise 完成
    await new Promise(function(resolve, reject) {
      // 此处通常有一个异步函数
      // 通过等待一秒来模拟它
      setTimeout(() => {
        // 初始化会话
        initStore(store, context)
        // 成功
        resolve()
      }, 1000)
    })
  }
}

function initStore(store, context) {
  // 当前请求中是否存在会话cookie
  const cookies = context.app.$cookies
  const session = cookies.get('session')
  if (!session) {
    // 不存在会话
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // 初始化存储
    store.commit('increment', 77)
  } else {
    console.log("nuxtServerInit, reprise d'une session existante")
    // 使用会话cookie更新存储
    store.commit('replace', session.store)
  }
  // 将存储器写入会话cookie
  cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
  // 日志
  console.log('initStore terminé, store=', store.state)
}

注释

  • 第2-5行:存储区将由一个计数器组成;
  • 第 9-11 行:该计数器可以递增;
  • 第13-17行:存储器的状态可从新状态初始化。此功能旨在展示当存储器不限于此处的计数器时,其可能的初始化方式;
  • 第21-35行:函数[nuxtServerInit]未作更改;
  • 第 30 行:当一秒的等待时间结束时,使用第 38-56 行中的函数初始化存储器;
  • 第40-41行:首先获取名为“session”的Cookie:
    1. 在应用程序首次运行时以及向服务器发送的第一个请求中,该 Cookie 尚不存在。此时将创建该 Cookie(第 53 行)并将其发送至客户端浏览器;
    2. 在同一应用程序运行期间,当向服务器发出第2、第3……次请求时,该Cookie将存在,因为客户端浏览器会在每次向服务器发出新请求时将其一并发送;
    3. 在第二次运行应用程序并向服务器发出第一个请求时,该 Cookie 也可能存在。实际上,在步骤 1 完成后,Cookie 已被存储在浏览器中,并具有一定的有效期。如果未超过该有效期,名为“session”的 Cookie 将随向服务器发出的第一个请求一并发送

简而言之,对于每次向服务器发出的请求:如果“session”Cookie已存储在客户端浏览器中,则服务器将收到它;否则,服务器将无法收到它。

  • 第42-47行:如果服务器未收到会话Cookie,则第46行将初始化存储;
    • 随后在第53行,将创建一个名为“session”的Cookie并将其放入服务器的HTTP响应中。该Cookie的值为对象[{ store: store.state }]。因此,放入会话Cookie中的是存储器的状态,而非存储器本身;
    • 函数 [set] 的第三个参数是一个选项对象:
      • [path] 指定该 Cookie 应返回给哪个 URL。 [context.base] 是应用程序 [nuxt-05] 的基础 URL。该基础对象在文件 [nuxt.config.js] 中定义:

  // 路由器
  router: {
    // 应用程序的 URL 根目录
    base: '/nuxt-05/'
},
      • [maxAge] 是浏览器中 Cookie 的有效期(以秒为单位)。超过此时间后,浏览器将不再将其发回服务器。[context.env.maxAge] 同样返回了 [nuxt.config.js] 文件中记录的值:
1
2
3
4
// 环境
  env: {
    maxAge: 60 * 5
}

[env] 是配置文件中的保留关键字。此处将有效期设置为 5 分钟。该时长以浏览器上次接收会话 Cookie 时为基准进行计算。超过此时间后,Cookie 将不再发回给服务器,服务器届时需启动新的会话;

  • 第 48-50 行:如果服务器收到会话 Cookie,则使用该 Cookie 中的 [store] 对象初始化存储状态。需要注意的是,该对象包含存储的保存状态;
    • 随后在第53行,会将会话cookie放入发给客户端浏览器的响应中:
      • 函数 [get] 将从服务器接收请求中提取会话 Cookie;
      • 函数 [set] 将会话 Cookie 放入服务器发给客户端浏览器的响应中;

8.5. 商店计数器的递增

[index.vue] 页面中的计数器递增过程如下:


  // 事件管理
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
       // 计数器递增 1
      this.$store.commit('increment', 1)
       // 更改显示值
      this.value = this.$store.state.counter
       // 将存储信息保存到会话cookie中
      this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
    }
}

在客户端,每次修改存储区时,都必须将其保存到会话cookie中。因为用户随时可能手动请求 URL,此时必须能够向服务器发送最新的存储区。 因此,在第 10 行,在增量存储计数器之后,我们将存储的状态保存到会话 Cookie 中:

  • Cookie 存储在 [this.$cookies] 属性中;
  • 存储器 [this.$store.state] 的状态保存在与键 [store] 关联的 Cookie 中;
  • Cookie的路径为[context.base]。在视图中,上下文可通过[this.$nuxt.context]获取;
  • Cookie 的生命周期为 [context.env.maxAge],此处可在属性 [this.$nuxt.context.env.maxAge] 中获取;

8.6. 运行示例 [nuxt-05]

我们启动应用程序 [nuxt-05]:

Image

以下截图来自Chrome浏览器。我们请求 URL [http://localhost:81/nuxt-05/]。请务必在 /nuxt-05 后面添加最后一个 /,否则将无法获得预期结果:

Image

  • 在 [4] 中,我们获得了存储器的初始值(77);

让我们查看浏览器日志(F12):

Image

  • 在 [5-6] 中,是服务器的日志;
  • 在 [7] 中,可以看到服务器启动了一个新会话。这意味着它未收到会话 Cookie;
  • 在 [8] 中,计数器初始化为 77;
  • 在 [9] 中,服务器(9)和客户端(10)的 [index] 页面确实显示了相同的计数器值;

现在来看浏览器接收到的cookie:

Image

  • 在 [1] 中,选择 [Application] 标签页,然后选择 [Cookies] [2] 选项。 在浏览器的所有Cookie中,选择来自[http://localhost:81]域的Cookie;
  • 在 [4] 中,找到名为“session”的 Cookie。若未找到,请刷新页面 [F5]:可能是该 Cookie 的 5 分钟有效期已过;
  • 在 [5] 中,是 Cookie 的值。尽管由于字符编码 { : 的影响,内容不太清晰,但仍可辨认出计数器的值 77;
  • 在 [6] 中,是该 Cookie 的 URL 部分:每当请求该 URL 时,浏览器都会将 Cookie 发送给服务器;
  • 在 [7] 中,表示 Cookie 的过期时间。当超过该时间后,浏览器将销毁该 Cookie;

请确保拥有该 Cookie。若未拥有,请刷新页面(F5)。当页面包含该 Cookie 时,请再次刷新页面(F5)。此时日志将变为如下内容:

Image

这次在 [3] 中,服务器确实成功获取了会话 Cookie。这是客户端浏览器发送给服务器的。

现在,请逐步增加计数器值,然后不时刷新当前页面(F5),无论是[index]还是[page1], 你会发现计数器并未像示例 [nuxt-04] 那样重置为 77,而是保留了页面刷新前客户端浏览器中的原始值:

Image

Image

此时浏览器的日志如下:

Image

:在测试过程中,您可能需要删除存储在浏览器中的会话 Cookie [5],以便在下次向服务器发送请求时,由服务器初始化一个新的会话。

最后,让我们展示[index]页面中的[incrementCounter]函数对存储在客户端浏览器中的会话Cookie的影响:


// 事件管理
  methods: {
    incrementCounter() {
      console.log('incrementCounter')
      // 计数器加1
      this.$store.commit('increment', 1)
      // 更改显示值
      this.value = this.$store.state.counter
      // 将存储值保存到会话cookie中
      this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
    }
  }
  • 第 10 行:计数器的修改会反映在会话 Cookie 中;

让我们验证这一点。我们从以下情况开始:

Image

  • 在 [4] 时,会话 Cookie 的计数器确实反映了显示的值 [1];

现在,我们将计数器递增一次,变为 [5]。会话 Cookie 从 [4] 演变为如下形式:

Image

  • 变为 [7],会话 Cookie 的计数器确实已变为 84。要查看这一点,需要刷新视图 [8]。 操作方法:先在 [Storage] 中选择 [9] 选项,然后重新选择 [8] 选项。此时,会话 Cookie 的新值应会显示出来;