Skip to content

16. 示例 [nuxt-13]:检查 [nuxt-12] 的导航

在此示例中,我们将重点放在 [nuxt-12] 的导航上。我们在 [nuxt-12] 中并未处理这一部分,因为控制导航会给本已复杂的示例增加额外的复杂性。

目标:我们希望用户只能执行授权操作:

  • 如果 JSON 会话尚未开始,则仅允许访问 URL [/];
  • 如果 JSON 会话已启动但用户未经过身份验证,则仅允许访问 URL [/authentication];
  • 如果 JSON 会话已启动且用户已通过身份验证,则仅允许访问 URL [/get-admindata, /end-session];
  • 当当前路由目标未获授权时,将重定向至获授权的 URL;

[nuxt-13] 示例最初是通过复制 [nuxt-12] 示例获得的:

Image

相关修改将在路由文件夹 [middleware] 中进行。

16.1. [nuxt] 应用程序的路由配置

应用程序的路由在 [nuxt.config] 文件中配置如下:


// router
  router: {
    // application URL root
    base: '/nuxt-13/',
    // routing middleware
    middleware: ['routing']
},
  • 第 6 行:应用程序的路由由 [middleware/routing] 文件控制;

[middleware/routing] 文件内容如下:


/* eslint-disable no-console */
 
// on importe les middleware du serveur et du client
import serverRouting from './server/routing'
import clientRouting from './client/routing'
 
export default function(context) {
  // qui exécute ce code ?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.server) {
    // routage serveur
    serverRouting(context)
  } else {
    // routage client
    clientRouting(context)
  }
}
  • 第 10–16 行:客户端和服务器端的 [nuxt] 路由处理方式不同。这是两者之间的主要区别;
  • 第 4 行:服务器端路由由脚本 [middleware/server/routing] 实现;
  • 第 5 行:客户端路由由 [middleware/client/routing] 脚本实现;

16.2. [nuxt] 客户端路由

[nuxt] 的客户端路由与 [nuxt-12] 中的实现保持一致:


/* eslint-disable no-console */
export default function(context) {
  // who executes this code?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // management of the PHP session cookie in the browser
  // the browser's PHP session cookie must be identical to the one found in the nuxt session
  // acion [fin-session] receives a new cookie PHP (server as nuxt client)
  // if the server receives it, the client must pass it on to the browser
  // for its own exchanges with the PHP server
  // this is customer routing
 
  // retrieve the session cookie PHP
  const phpSessionCookie = context.store.state.phpSessionCookie
  if (phpSessionCookie) {
    // if it exists, we assign the PHP session cookie to the browser
    document.cookie = phpSessionCookie
  }
 
  ...
}

为了防止客户端访问未经授权的路由,我们只需在客户端导航菜单中提供授权的路由即可。[components/navigation] 组件将变为如下所示:


<template>
  <!-- bootstrap menu with three options -->
  <b-nav vertical>
    <b-nav-item v-if="$store.state.jsonSessionStarted && !$store.state.userAuthenticated" to="/authentification" exact exact-active-class="active">
      Authentification
    </b-nav-item>
    <b-nav-item
      v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated && !$store.state.adminData"
      to="/get-admindata"
      exact
      exact-active-class="active"
    >
      Requête AdminData
    </b-nav-item>
    <b-nav-item v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated" to="/fin-session" exact exact-active-class="active">
      Fin session impôt
    </b-nav-item>
  </b-nav>
</template>
  • 第 4 行:[Authentication] 选项仅在 JSON 会话已启动但用户尚未通过身份验证时可用。如果 JSON 会话尚未启动或用户已通过身份验证,则该选项不可用;
  • 第 7–11 行:[AdminData Request] 选项仅在 JSON 会话已启动、用户已通过身份验证且 [AdminData] 尚未检索的情况下可用。如果这三个条件中的任何一个未满足(JSON 会话未启动、用户未通过身份验证或 [AdminData] 数据已检索),则该选项不可用;
  • 第 15 行:只要 JSON 会话已启动且用户已通过身份验证,[End Tax Session] 选项即可用;否则不可用;

16.3. 服务器路由 [Nuxt]

服务器路由通常比客户端路由更为复杂,因为用户可以在浏览器的地址栏中输入任意 URL。我们可以放任这种情况发生(毕竟用户本不该这样做),也可以尝试加以控制。 出于本示例的考虑,我们将采取后者,因为在 [nuxt-12] 应用中,由于税务计算服务器已针对这些手动输入的 URL 进行了充分防护,并且知道如何发送相应的错误信息,因此我们可以轻松地不进行路由控制。我们在 [next-12] 中已经看到过这种情况,那里没有进行路由控制。

在重定向方面,[nuxt] 服务端的路由机制与 [nuxt] 客户端的路由机制有很大不同:

  • 当 [nuxt] 服务器被重定向时,它会向客户端浏览器发送包含重定向目标的请求。随后,浏览器会向 [nuxt] 服务器发起新的请求,以获取收到的目标。 这就像用户手动输入了重定向目标的 URL 一样:整个 [nuxt] 应用会重启,其整个生命周期(服务器插件、数据存储、服务器路由、页面)也会随之重置;
  • 当 [nuxt] 客户端发生重定向时,上述情况均不会发生。此时仅发生页面切换,与用户点击指向重定向目标的链接时完全相同。此时的生命周期则有所不同(客户端路由、渲染路由目标);

因此,尽管两者的代码基础可能看起来相似,但最好还是将客户端路由与服务器端路由分开。

服务器路由脚本 [middleware/server/routing] 将如下所示:


/* eslint-disable no-console */
export default function(context) {
  // qui exécute ce code ?
  console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
 
  // on récupère quelques informations dans le store [nuxt]
  const store = context.store
  // d'où vient-on ?
  const from = store.state.from || 'nowhere'
  ...
}
  • 在客户端路由中,路由函数会接收一个名为 [context] 的上下文对象,其中包含 [context.from] 属性,该属性表示我们来自的页面路由。我们要前往的路由可通过 [context.route] 获取;
  • 在服务器端路由中,路由函数接收的上下文 [context] 不包含 [context.from] 属性。服务器端路由仅在手动向 [nuxt] 服务器请求 URL 时发生。我们知道此时整个 [nuxt] 应用会被重置。这就像是从头开始一样,因此不存在“上一页”的概念;
  • 得益于 [nuxt] 会话,我们知道服务器可以检索该会话,从而避免从头开始。因此,我们将在这个 [nuxt] 会话中(更具体地说,是在会话的存储中)存储客户端浏览器在向 [nuxt] 服务器请求 URL 之前显示的最后一个页面的名称;
  • 第 7–9 行:我们获取客户端浏览器显示的最后一个页面的名称。应用程序启动时,存储中尚不存在此信息。随后,我们将该名称赋值给变量 `from`;

为了让 [nuxt] 服务器能够从存储中获取客户端浏览器最后显示的页面名称,[nuxt] 客户端也必须将此信息写入存储。因此,[nuxt] 客户端的路由脚本如下所示:


/* eslint-disable no-console */
export default function(context) {
  // qui exécute ce code ?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // gestion du cookie de la session PHP dans le navigateur
  // le cookie de la session PHP du navigateur doit être identique à celui trouvé en session nuxt
  // l'acion [fin-session] reçoit un nouveau cookie PHP (serveur comme client nuxt)
  // si c'est le serveur qui le reçoit, le client doit le transmettre au navigateur
  // pour ses propres échanges avec le serveur PHP
  // on est ici dans un routing client
 
  // on récupère le cookie de la session PHP
  const phpSessionCookie = context.store.state.phpSessionCookie
  if (phpSessionCookie) {
    // s'il existe, on affecte le cookie de session PHP au navigateur
    document.cookie = phpSessionCookie
  }
 
  // on met dans la session le nom de la page où on va - pas de redirection serveur
  context.store.commit('replace', { serverRedirection: false, from: context.route.name })
  // on sauvegarde le store dans la session [nuxt]
  const session = context.app.$session()
  session.value.store = context.store.state
  session.save(context)
}
  • 添加了第 19–24 行;
  • 第 20 行:我们将页面名称 [context.route.name] 存储在存储中,该名称将在后续路由步骤中作为我们来自的页面。 此外,我们将会看到,在 [nuxt] 服务器的路由中,它需要知道当前路由是否源自 [nuxt] 服务器之前的重定向。这里并非如此,因此我们将 [serverRedirection] 属性设置为 [false];
  • 第 22–24 行:将存储的状态放入 [nuxt] 会话中(第 23 行),然后将 [nuxt] 会话保存到 Cookie 中(第 24 行),该 Cookie 随后将保存在 [nuxt] 客户端的浏览器中;

让我们回到 [nuxt] 服务器的路由脚本:


/* eslint-disable no-console */
export default function(context) {
  // qui exécute ce code ?
  console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
 
  // on récupère quelques informations dans le store [nuxt]
  const store = context.store
  // d'où vient-on ?
  const from = store.state.from || 'nowhere'
  // où va-t-on ?
  const to = context.route.name
  // éventuelle redirection
  let redirection = ''
  // gestion du routage terminé
  let done = false
 
  // est-on déjà dans une redirection du serveur [nuxt]?
  if (store.state.serverRedirection) {
    // rien à faire
    done = true
  }
 
  // est-ce un rechargement de page ?
  if (to === from) {
    // rien à faire
    done = true
  }
 
  // contrôle de la navigation du serveur [nuxt]
  // on s'inspire de la navigation client dans le composant [navigation]
 
  // cas où la session PHP n'a pas démarré
  if (!done && !store.state.jsonSessionStarted && to !== 'index') {
    // redirection
    redirection = 'index'
    // travail terminé
    done = true
  }
 
  // cas où l'utilisateur n'est pas authentifié
  if (!done && store.state.jsonSessionStarted && !store.state.userAuthenticated && to !== 'authentification') {
    // redirection
    redirection = from
    // travail terminé
    done = true
  }
 
  // cas où l'utilisateur a été authentifié
  if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && to !== 'get-admindata' && to !== 'fin-session') {
    // on reste sur la même page
    redirection = from
    // travail terminé
    done = true
  }
 
  // cas où [adminData] a été obtenu
  if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && store.state.adminData && to !== 'fin-session') {
    // on reste sur la même page
    redirection = from
    // travail terminé
    done = true
  }
 
  // on a fait tous les contrôles ---------------------
  // redirection ?
  if (redirection) {
    // on note la redirection dans le store
    store.commit('replace', { serverRedirection: true })
  } else {
    // pas de redirection
    store.commit('replace', { serverRedirection: false, from: to })
  }
  // on sauvegarde le store dans la session [nuxt]
  const session = context.app.$session()
  session.value.store = store.state
  session.save(context)
  // on fait l'éventuelle redirection
  if (redirection) {
    context.redirect({ name: redirection })
  }
}
  • 第 6–9 行:从 [nuxt] 服务器存储中获取 [from] 的值;
  • 第 11 行:记录当前路由的目标;
  • 第 13 行:路由操作可能会导致客户端浏览器重定向。[redirection] 将作为此次重定向的目标;
  • 第 15 行:将 [done] 设为 [true] 表示路由已完成;
  • 第 17–21 行:首先,我们检查当前路由是否源于发送到客户端浏览器的重定向请求。该信息存储在存储器的 [serverRedirection] 属性中。如果该属性为 true,则表示在之前向 [nuxt] 服务器发送的请求中,[nuxt] 服务器已向客户端浏览器发送了重定向。在这种情况下,无需进行路由。 在上一次请求中,[nuxt] 服务器路由器已决定将客户端浏览器重定向。这一决定无需被新的路由覆盖;
  • 第 23–27 行:我们检查当前路由是否为页面刷新。如果是,则允许其继续执行;
  • 从第 29 行开始,我们恢复 [nuxt] 客户端 [navigation] 组件中应用的规则(参见上一段);
  • 第 32–38 行:处理 JSON 会话尚未开始且路由目标不是 [index] 页面的情况。此时,我们将客户端浏览器重定向至 [index] 页面;
  • 第 40–46 行:处理 JSON 会话已启动、用户未经过身份验证且当前路由目标不是 [authentication] 页面的情况。在此情况下,我们拒绝该路由请求并保持当前状态;
  • 第 48–54 行:处理 JSON 会话已启动、用户已认证,且当前路由目标既非 [get-admindata] 页面也非 [end-session] 页面的情况(此时仅此两种页面为可能的去向)。在此情况下,拒绝请求的路由,并返回原位置;
  • 第 56–62 行:处理已获取 [adminData] 的情况。此时,唯一可能的路由目标是 [fin-session] 页面。如果请求的页面并非该页面,则拒绝该路由并返回原位置;
  • 第 64–72 行:如果发生了重定向,我们会将其记录在 [nuxt] 服务器存储中:[serverRedirection: true]。 请注意,我们并未为存储的 [from] 属性赋值。原因是客户端浏览器会进行重定向,而我们看到在此情况下并未发生路由(第 17–20 行),因此存储的 [from] 属性不会被使用;
  • 第 66–69 行:如果没有发生重定向,我们也会在 [nuxt] 服务器存储中记录这一点:[serverRedirection: false]。此外,当前路由将显示 [to] 页面,该页面在接下来的请求(客户端或 [nuxt] 服务器)中将成为上一页。这就是为什么我们写 [from: to];
  • 第 73–76 行:我们将存储保存到 [nuxt] 会话中,该会话本身保存在 Cookie 中;
  • 第 77–80 行:如果 [redirection] 不为空,则指示浏览器进行重定向。否则(此处未显示),[nuxt] 服务器的生命周期将继续:[to] 页面将由 [nuxt] 服务器处理,并连同 [nuxt] 会话 Cookie 一起发送至 [nuxt] 客户端的浏览器;

此处为 [nuxt] 服务器选择的路由方案是任意的。我们本可以选择其他方案,或者如前所述,完全不使用路由。上述方案的优势在于,无论用户请求的 URL 为何,都能始终保持应用程序处于稳定状态。

当最终加载的页面是原始页面时,我们可以稍作改进。有两种情况:

  • 用户触发了页面刷新(to===from);
  • 存在重定向至原始页面的情况(redirection===from);

在这两种情况下,原始页面都会再次执行,并向税费计算服务器发起异步调用。让我们举个例子。如果用户在认证通过后刷新页面(按 F5)。在此情况下,在上述路由中,我们有:[to]=[from]=[authentication]。 此时不存在重定向。[to=authentication] 页面将由 [nuxt] 服务器执行。如果我们不采取任何措施,[asyncData] 函数将再次运行。由于身份验证已经完成,这完全没有必要。

我们可以通过稍微修改 [authentication] 页面来改进这一点:


// asynchronous data
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    // don't do things twice if the page has already been requested
    if (process.server && context.store.state.userAuthenticated) {
      console.log('[authentification asyncData canceled]')
      return { result: '[succès]' }
    }
      // customer [nuxt]
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // authenticate to the server
...
  • 第 6-9 行:如果页面由 [nuxt] 服务器提供,且我们在存储中发现已进行过身份验证,则直接返回预期结果(第 8 行);

对所有页面我们都采用同样的处理方式:

页面 [index]


// asynchronous data
  async asyncData(context) {
    // log
    console.log('[index asyncData started]')
    // don't do things twice if the page has already been requested
    if (process.server && context.store.state.jsonSessionStarted) {
      console.log('[index asyncData canceled]')
      return { result: '[succès]' }
    }
    try {
...

页面 [get-admindata]


// asynchronous data
  async asyncData(context) {
    // log
    console.log('[get-admindata asyncData started]')
    // don't do things twice if the page has already been requested
    if (process.server && context.store.state.adminData) {
      console.log('[get-admindata asyncData canceled]')
      return { result: context.store.state.adminData }
    }
    // customer
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
   ...  

页面 [结束会话]


// asynchronous data
  async asyncData(context) {
    // log
    console.log('[fin-session asyncData started]')
    // don't do things twice if the page has already been requested
    if (process.server && context.store.state.jsonSessionStarted && !context.store.state.userAuthenticated) {
      console.log('[fin-session asyncData canceled]')
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    }
    // customer case [nuxt]
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
 

16.4. 执行

要运行此示例,请务必在执行前从运行 [nuxt] 客户端的浏览器中删除 [nuxt] 会话 Cookie 和 PHP Cookie,以便从头开始。以下是使用 Chrome 浏览器的示例:

Image

16.5. 结论

[nuxt] 服务器的路由机制较为复杂,因为您必须预先考虑用户可能手动输入的每一个 URL。这只是一个教科书式的示例。[nuxt] 应用程序并不建议以这种方式使用。一旦 [index] 页面由 [nuxt] 服务器的路由器返回,后续发往服务器的请求可能会被重定向到错误页面。

就我们 [nuxt-13] 示例的具体情况而言,[nuxt] 服务器路由是多余的。[nuxt-12] 示例中的默认行为(本质上是不进行路由)已经完全能够满足需求。