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] 示例获得的:

相关修改将在路由文件夹 [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 浏览器的示例:

16.5. 结论
[nuxt] 服务器的路由机制较为复杂,因为您必须预先考虑用户可能手动输入的每一个 URL。这只是一个教科书式的示例。[nuxt] 应用程序并不建议以这种方式使用。一旦 [index] 页面由 [nuxt] 服务器的路由器返回,后续发往服务器的请求可能会被重定向到错误页面。
就我们 [nuxt-13] 示例的具体情况而言,[nuxt] 服务器路由是多余的。[nuxt-12] 示例中的默认行为(本质上是不进行路由)已经完全能够满足需求。