在 Nuxt 中编写路由中间件
和往常一样,一旦我们理解了中间件在 Vue
中的工作方式,那么在 Nuxt
中使用它就会更容易,因为 Nuxt
已经为我们处理了 Vue Router
。在接下来的章节中,我们将学习如何在 Nuxt
应用中使用全局和单个路由中间件。
在 Nuxt
中,所有中间件都应该放在 /middleware/
目录下,中间件的文件名将是中间件的名称。例如,/middleware/user.js
就是 user
中间件。一个中间件将 Nuxt
上下文作为其第一个参数:
export default (context) => { ... }
此外,中间件可以是异步的:
export default async (context) => {
const { data } = await axios.get('/api/path')
}
在通用模式下,中间件在服务器端只调用一次(例如,首次请求 Nuxt
应用或刷新页面时),然后在客户端导航到其他路由时调用。另一方面,无论你是首次请求应用还是在首次请求后导航到其他路由,中间件总是在客户端调用。中间件首先在 Nuxt
配置文件中执行,然后是布局,最后是页面。我们现在将在下一节开始编写一些全局中间件。
编写全局中间件
添加全局中间件非常简单;你只需要在配置文件的 router
选项中的 middleware
键中声明它们。例如,请看以下代码:
// nuxt.config.js
export default {
router: {
middleware: 'auth'
}
}
现在,让我们在以下步骤中创建一些全局中间件。在本练习中,我们想要从 HTTP
请求头中获取用户代理信息,并跟踪用户正在导航的路由:
-
在
/middleware/
目录中创建两个中间件,一个用于获取用户代理信息,另一个用于获取用户正在导航的路由路径信息:// middleware/user-agent.js export default (context) => { context.userAgent = process.server ? context.req.headers[ 'user-agent'] : navigator.userAgent } // middleware/visits.js export default ({ store, route, redirect }) => { store.commit('addVisit', route.path) }
-
在
router
选项的middleware
键中声明上述中间件,如下所示:// nuxt.config.js module.exports = { router: { middleware: ['visits', 'user-agent'] } }
请注意,在
Nuxt
中,我们不需要像在Vue
应用中那样使用第三方包来调用多个守卫。 -
创建
store
的state
和mutations
来存储访问过的路由:// store/state.js export default () => ({ visits: [] }) // store/mutations.js export default { addVisit (state, path) { state.visits.push({ path, date: new Date().toJSON() }) } }
-
在
about
页面中使用user-agent
中间件:// pages/about.vue <template> <p>{{ userAgent }}</p> </template> <script> export default { asyncData ({ userAgent }) { return { userAgent } } } </script>
-
至于
visits
中间件,我们想在一个组件中使用它,然后将这个组件注入到我们的布局中,即default.vue
布局。首先,在/components/
目录中创建visits
组件:// components/visits.vue <template> <ul> <li v-for="(visit, index) in visits" :key="index"> <i>{{ visit.date | dates }} | {{ visit.date | times }}</i> - {{ visit.path }} </li> </ul> </template> <script> export default { filters: { dates(date) { return date.split('T')[0] }, times(date) { return date.split('T')[1].split('.')[0] } }, computed: { visits() { return this.$store.state.visits.slice().reverse() } } } </script>
因此,我们在这个组件中创建了两个过滤器。
date
过滤器用于从字符串中获取日期。例如,我们将从 2019-05-24T21:55:44.673Z 得到 2019-05-24。相比之下,time
过滤器用于从字符串中获取时间。例如,我们将从 2019-05-24T21:55:44.673Z 得到 21:55:44。 -
将
visits
组件导入到我们的布局中:// layouts/default.vue <template> <div> <nuxt /> <Visits /> </div> </template> <script> import Visits from '~/components/visits.vue' export default { components: { Visits } } </script>
当我们在不同的路由之间导航时,应该在浏览器中看到以下结果:
2019-06-06 | 01:55:44 - /contact 2019-06-06 | 01:55:37 - /about 2019-06-06 | 01:55:30 - /
此外,当你在
about
页面时,你应该从请求头中获取到用户代理的信息:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36
你可以在我们的
GitHub
仓库的/chapter-11/nuxt-universal/route-middleware/global/
中找到上述源代码。
全局中间件就介绍到这里。现在,让我们在下一节继续学习单个路由中间件。
编写路由前置的中间件
添加单个路由中间件也非常简单;你只需要在特定布局或页面的 middleware
键中声明它们。例如,请看以下代码:
// pages/index.vue 或 layouts/default.vue
export default {
middleware: 'auth'
}
因此,让我们在以下步骤中创建一些单个路由中间件。在本练习中,我们将使用会话和 JSON Web
令牌 (JWT
) 来访问受限页面或受保护的 API
。虽然在现实生活中,我们可以只使用会话或令牌进行身份验证系统,但我们将两者都用于我们的练习,以便我们知道如何在潜在的更复杂的生产系统中一起使用它们。在我们的练习中,我们将希望用户登录并从服务器获取令牌。当令牌过期或无效时,用户将无法访问受保护的路由。
此外,当会话时间结束时,用户将被注销:
-
创建一个
auth
中间件来检查我们store
中的state
是否有任何数据。如果没有经过身份验证的数据,那么我们使用Nuxt
上下文中的error
函数将错误发送到前端:// middleware/auth.js export default function ({ store, error }) { if (!store.state.auth) { error({ message: '你尚未连接', statusCode: 403 }) } }
-
创建一个
token
中间件以确保令牌在store
中;否则,它会将错误发送到前端。如果令牌存在于store
中,我们将带有令牌的Authorization
设置为默认的axios header
:// middleware/token.js import axios from 'axios' export default async ({ store, error }) => { if (!store.state.auth.token) { error({ message: '没有令牌', statusCode: 403 }) } axios.defaults.headers.common['Authorization'] = `Bearer: ${store.state.auth.token}` }
-
将这两个前面的中间件添加到受保护页面的
middleware
键中:// pages/secured.vue <template> <p>{{ greeting }}</p> </template> <script> import axios from 'axios' export default { async asyncData ({ redirect }) { try { const { data } = await axios.get('/api/private') return { greeting: data.data.message } } catch (error) { if(process.browser){ alert(error.response.data.message) } return redirect('/login') } }, middleware: ['auth', 'token'] } </script>
在
headers
中使用JWT
设置Authorization header
后,我们可以访问受保护的API
路由,这些路由受服务器端中间件保护(我们将在第十二章 “创建用户登录和API
身份验证” 中了解更多信息)。我们将从我们要访问的受保护的API
路由获取数据,如果令牌不正确或已过期,将收到错误消息提示。 -
在
/store/
目录中创建store
的state
、mutations
和actions
以存储身份验证数据:// store/state.js export default () => ({ auth: null }) // store/mutations.js export default { setAuth (state, data) { state.auth = data } } // store/actions.js import axios from 'axios' export default { async login({ commit }, { username, password }) { try { const { data } = await axios.post('/api/public/users/login', { username, password }) commit('setAuth', data.data) } catch (error) { // 处理错误 } }, async logout({ commit }) { await axios.post('/api/public/users/logout') commit('setAuth', null) } }
一个已知且预期的行为是,当页面刷新时,store
的 state
将重置为默认值。如果我们想持久化 state
,可以使用以下几种解决方案:
-
localStorage
-
sessionStorage
-
vuex-persistedstate (一个 Vuex 插件)
然而,在我们的例子中,由于我们使用 session
来存储身份验证信息,我们实际上可以通过以下方式从 session
中重新获取我们的数据:
-
req.ctx.session (Koa) 或 req.session (Express)
-
req.headers.cookie
一旦我们决定了我们想要使用的解决方案或选项(假设是 req.headers.cookie
),那么我们可以按如下方式重新填充 state
:
// store/index.js
const cookie = process.server ? require('cookie') : undefined
export const actions = {
nuxtServerInit({ commit }, { req }) {
var session = null
var auth = null
if (req.headers.cookie && req.headers.cookie.indexOf('koa:sess') > -1)
{
session = cookie.parse(req.headers.cookie)['koa:sess']
}
if (session) {
auth = JSON.parse(Buffer.from(session, 'base64').toString('utf8'))
commit('setAuth', auth)
}
}
}
你可以在我们的 |
当遵循所有上述步骤并创建中间件后,我们可以使用 npm run dev
运行这个简单的身份验证应用,看看它是如何工作的。我们将在下一章学习服务器端身份验证。现在,我们只需要关注中间件并理解它的工作原理,这将有助于我们学习下一章。现在,让我们继续本章的最后一部分——服务器中间件。