分享一个Nuxt.js项目实战经历

穿职业装的程序媛
1.  为什么使用Nuxt.js?
2.  nuxt项目搭建
3.  异步数据加载
4.  token的处理逻辑
5.  mysql接入
6.  session的持久化处理

1. 为什么使用Nuxt.js?

    在了解Nuxt.js之前,我们先来了解一下前端页面发展过程。Web网站从诞生至今的二十多年时间你,前端资源分配比例依次经历了  少量HTML + 少量CSS、大量HTML + 大量CSS + 少量JavaScript、大量HTML + 大量CSS + 大量JavaScript、少量HTML + 大量CSS + 大量JavaScript。并且all-in-js的趋势越来越明显。在React\Vue\Angular等开发框架的支持下,HTML和CSS都可以有js编写,经编译后产生CSS代码和用于浏览器环境下渲染的HTML的JavaScript代码。由于JavaScript的起步晚于HTML和CSS,最初的Web页面都是静态的HTML,后来才逐步加入CSS和JavaScript。SEO爬虫软件通过分析网站的HTML文档抓取信息,但并不能支持抓取JavaScript脚本,即便是在React和Vue等浏览器端渲染方案CSR大行其道的今天,也仅有google爬虫能初步简单支持SPA,这还需要在编写代码时做特殊处理。传统的服务器端渲染SSR方案是借助服务端编程语言或者框架搭配的HTML模板引擎将数据编译为HTML文档,比如适用于Java的FreeMaker和适用于Node.js的Pug等。模板引擎可以被理解为一个功能强大的字符串处理工厂。

  Nuxt.js是当下常用的的服务器端渲染方案SSR。SSR相对于CSR最主要的优势在于其支持SEO(搜索引擎优化)和首屏时间短,可以在服务端把主要内容获取完成并生成HTML发送到浏览器端,这个过程中可以提供更加详细的信息供SEO抓取。而常用是SPA方案则通常是返回一个仅有及时行代码的html文档和一大堆JavaScript文件到浏览器端,HTML文档中几乎不包含页面重要信息,页面内容主要由JavaScript文件中的逻辑产生并添加到dom中。在首屏渲染方面,现在的很多大中型项目在首页就会请求大量数据,SSR项目中可以把首屏需要的关键数据请求放在服务端完成,大多数情况下服务端网络设施是明显强于C端浏览器的,这样能节省不少请求时间,用户看到的页面,是一个已经带有关键数据的可用页面。而SPA页面往往是在页面加载完成之后再去发送网络请求,白屏时间会更长,用户体验不佳。

   但是并不是所有的前端项目都推荐使用SSR,由于Nuxt.js和Next.js等框架的支持能力目前还不足,SSR项目中开发前端还是有很多地方会有掣肘,比如生命周期和常用钩子不如CSR SPA项目那样丰富。但是在对SEO和首屏渲染时间具有强需求的项目中,如企业官网、产品列表等项目,我们希望页面能尽量被搜索引擎抓取到并且能尽可能减少用户的白屏等待时间,就可以采用SSR框架进行开发。

  Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。Nuxt.js实现了开箱即用的劝勉支持,利用 SSR,Node.js 服务器将基于 Vue 的组件渲染成 HTML 并传输到客户端,而不是纯 javascript。与传统的 Vue SPA 相比,使用 SSR 将带来巨大的 SEO 提升、更好的用户体验和更多的机会。

2. Nuxt.js项目搭建

   Nuxt.js团队创建了脚手架工具create-nuxt-app来搭建Nuxt.js项目,可采用如下命令创建:

`px create-nuxt-app <项目名>`

或者

`yarn create nuxt-app <项目名>`
创建中会让人选择集成的服务器框架(如Express\Koa\Hapi)等,还可以选择需要的UI框架、测试框架、开发模式等等。
我们也可以选择使用Express-template创建项目
`vue init nuxt-community/express-template nuxt-express`
当然也可以自己手动创建nuxt项目,从package.json安装nuxt依赖开始自己动手。
假如我们采用express-template创建项目,得到的项目目录如下:

|—— server        Node.js后台代码存放目录
    |—— routes                api接口代码文件目录
      |—— users.js    api接口代码文件
    |—— index.js              核心文件,接口层通用配置
|—— assets                     用于放置未编译的静态资源如 LESS、SASS
    |—— css                        
    |—— img
|—— components          普通组件目录
    |—— Footer.vue
|—— layouts                   布局文件目录
    |—— default.vue        默认布局(必须),也可以自己编写布局,在page中指定使用该布局文件,没有指定时,默认使用default布局
    |—— error.vue            页面出错时会默认展示的布局
|—— middleware           用于存放应用的中间件
|—— pages                      路由组件目录,每一个文件都将作为一个单独的页面呈现
   |—— _id.vue                路由组件文件(编译后会自动生成路由/:id)
    |—— index.vue            路由组件文件(对应路由/)
|—— plugins                    公共函数目录
    |—— axios.js
|—— static                       静态文件目录,该目录不会被webpack编译
|—— store                       用于组织应用的vuex状态树
|—— .eslintrc.js       
|—— .gitignore
|—— nuxt.config.js         用于自定义配置,可以覆盖默认配置
|—— package.json
|—— README.md
    

这里至关重要的就是nuxt.config.js文件,可以在该文件中个性化配置nuxt项目的部署方式、生成html的配置项(如title\head\css\plugins)、打包配置选项、路由配置、环境变量配置等等丰富的选项。

3. 异步数据加载

nuxt.js封装了$http$axios组件,可以让我们在前端页面中,像普通的SPA应用一样方便地使用这些网络请求模块在浏览器中向后台请求数据。

前面我们提到nuxt.js可以应用于SEO,它的特别之处就在于扩展了Vue.js,增加了一个叫asyncData 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,方法参数中默认提供了$http和$axios组件, 可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。这也就决定了我们可以在服务端完成首屏数据请求,并将数据填充到html中,再返回给浏览器。

nuxt项目可以仅仅作为一个中间层提供接口转发和服务器端渲染,也可以涵盖一个完整的后台提供服务。在官网项目中,我们的后台服务和nuxt应用是在同一个项目中的,首先我们在nuxt.config.js文件中指明了后台接口的请求地址,所有/api开头的接口都将向根目录下server目录中请求:

/*
** Server Middleware
*/
serverMiddleware: {
'/api': '~/server'
},

我们改造了上述server目录,使其可以成为一个独立的应用提供对内和对外的服务,整个服务就是一个express应用,改造后的server目录结构也更加清晰简单:

|—— server        Node.js后台代码存放目录
    |—— controllers         控制器目录,接口的逻辑处理等
      |—— users.js    user相关的控制器,与数据库表相关的处理时,会引用相应的models
   |—— middleware       接口中间件,可以做一些权限、cookie等方面的通用处理
      |—— check.js    
   |—— models                数据层目录
      |—— user.js      user有关的表的读取处理等逻辑,引用db.js
   |—— mysql                  数据库操作目录
      |—— db.js        数据库连接通用配置
       |—— sql.js        sql语句表,我们把所有的sql语句都放到这里,方便我们统一审核
    |—— routes                 api接口路由目录
        |—— index.js    路由分配的中间件
      |—— users.js    user相关的路由管理,引用user控制器和接口中间件
    |—— index.js              核心文件,接口层通用配置

4. token的处理逻辑

在系统中有注册登录等功能模块时,就需要用到token的管理。在nuxt项目后台中,同express应用一样,我们可以使用express-jwt来实现。

express-jwt有多种加密方案,我们这里采用rsa方案,先生成公钥和私钥保存在根目录。我们一般在用户登录成功之后生成token,并在返回报文中返回给浏览器,由浏览器保存并在下一个接口添加到headers中,express-jwt提供了现成的接口生成token:

// 注意默认情况 Token 必须以 Bearer+空格 开头
const privateKey = fs.readFileSync('rsa_private_key.pem')
const token = 'Bearer ' + jwt.sign(
  {
    user_id: user.id,
    isLogin: true
  },
  privateKey,
  {
    algorithm: 'RS256'
  }
)
return res.send({
  code: constants.SUCCESS,
  token,
  user,
  msg: '登录成功'
})

token的校验则是在接口通用逻辑server/index.js中处理,在接口请求中间件中使用express-jwt,会自行采用公钥校验token,我们可以设置不经过Token解析的接口路径。如果token校验通过,则接口进入下一环节,如果没有通过,则会返回UnauthorizedError的错误并被捕捉处理。

import fs from 'fs'
import express from 'express'
import expressJwt from 'express-jwt'
const createError = require('http-errors')

// Create express instance
const app = express()
const publicKey = fs.readFileSync('rsa_public_key.pem')
app.use(expressJwt({
  secret: publicKey, // 签名的密钥
  algorithms: ['RS256'] // 设置算法(官方文档上没有写这个,但是不配置的话会报错)
}).unless({
  path: ['/api/users/login', '/api/users/getcaptcha', '/api/test', /\/api\/portal/i] // 不经过 Token 解析的路径
}))
// error handler
app.use(function (err, req, res, next) {
  if (err.name === 'UnauthorizedError') {
    //  可以根据自己的业务逻辑来处理
    return res.send({
      code: '401',
      msg: '您还未登录,请先登录'
    })
  }
  // set locals, only providing error in development
  res.locals.message = err.message
  res.locals.error = req.app.get('env') === 'development' ? err : {}
  // render the error page
  res.status(err.status || 500)
  res.render('error')
})
module.exports = app

5. mysql的接入

不妨先了解一下config-lite这款小工具。它默认读取根目录下config文件夹下的文件,根据当前的系统环境development\production一次降级查找对应名称的js\json\node\yml\yaml等文件配置,并和default配置合并,产生当前环境下最终配置文件。我们可以通过它读取当前配置,而不用在切换环境变量时更改我们使用的配置。

我们可以使用常用的各种类型的数据库,express应用服务框架均由良好的接入能力。在研究院门户网站中我们采用常用的mysql数据库,这里需要使用到express接入mysql的连接工具mysql npm包。首先在config\development.js中我们定义数据库连接相关配置

module.exports = {
  mysql: {
    // 测试服务器
    host: '127.0.0.1',
    port: '3306',
    user: 'your-database-username',
    password: 'your-password',
    database: 'your-database-name',
    waitForConnections: true,
    connectionLimit: 50,
    queueLimit: 0
  }
}

在server\mysql\db.js中,我们定义数据库的接入方式。数据库的连接可以采用单个请求连接connection,也可以采用连接池pool,还有poolcluster等方案,具体的api可以在github中mysql包中查看。我们这里示例采用普通连接池,mysql npm工具会在连接完成之后自动关闭连接池。

import mysql from 'mysql'
const config = require('config-lite')({
  filename: process.env.NODE_ENV,
  config_basedir: __dirname,
  config_dir: 'config'
})
const pool = mysql.createPool(config.mysql)
export default pool

在models中我们就可以引用pool来进行数据库相关操作,如userModels,pool.query提供了数据库连接池的操作方法,方法第一个参数是一条sql语句

import pool from '../mysql/db'
import { UserSql } from '../mysql/sql'

findOne (param) {
    return new Promise(function (resolve, reject) {
      pool.query(UserSql.findOne, [param.username], function (error, results, fields) {
        if (error) {
          console.error(error)
          reject(error)
        }
        resolve(results[0])
      })
    })
}

6. session的持久化处理

会话我们同样需要用到express的小工具express-session,在config\default.js中我们需要配置session的参数:

module.exports = {
  port: parseInt(process.env.PORT, 10) || 8001,
  session: {
    name: 'aaa',
    secret: 'bbb',
    id: '',
    captcha: '',
    cookie: {
      httpOnly: true,
      secure: false,
      maxAge: 365 * 24 * 60 * 60 * 1000
    }
  }
}

配置参数中声明了cookie的相关参数、token的生成密钥等相关参数。在server\index.js文件中,我们引入session中间件

import session from 'express-session'
app.use(session({
  name: config.session.name,
  secret: config.session.secret,
  resave: false,
  captcha: config.session.captcha,
  saveUninitialized: false,
  cookie: config.session.cookie
}))

但是这样并没有实现session持久化存储,在开发过程中重启项目后发生session丢失。express有针对session在mysql数据库中持久化存储的小工具,我们需要在在server\mysql\db.js中初始化sessionStore:

import mysql from 'mysql'
const config = require('config-lite')({
  filename: process.env.NODE_ENV,
  config_basedir: __dirname,
  config_dir: 'config'
})
const session = require('express-session')
const MySQLStore = require('express-mysql-session')(session)
const pool = mysql.createPool(config.mysql)
export const sessionStore = new MySQLStore({}/* session store options */, pool)
export default pool

在server\index.js文件中,判断接口session时,加入持久化配置,这样会自动在mysql数据库中添加一张sessions表,存储session_id、expires、data等信息。我们可以在必要的时候对持久化的session做destroy clear等操作,也可以将持久化的session reload到内存中。

import { sessionStore } from './mysql/db'
import session from 'express-session'
app.use(session({
  name: config.session.name,
  secret: config.session.secret,
  resave: false,
  captcha: config.session.captcha,
  saveUninitialized: false,
  cookie: config.session.cookie,
  store: sessionStore
}))

以上就是第一次用nuxt.js做项目的一点小小的总结,欢迎大家批评指正

阅读 354
19 声望
3 粉丝
0 条评论
你知道吗?

19 声望
3 粉丝
宣传栏