koa2初嘗試

原文首發於個人博客,歡迎點擊查看得到更好的閱讀體驗~javascript

前言

一直都想接觸下服務端的內容,之前看過python基礎,可是由於在工做中基本上使用不到,因此基礎看了幾遍沒有實際項目操做就忘記了,忘~記~了~html

朝秦暮楚後最終選擇了基於Nodejs的中間件Koa2完成了一個簡單的RESTful風格的API項目。前端

項目中主要完成了如下API接口:vue

  • 登陸
  • 用戶的增刪改查
  • 圖片上傳、視頻上傳以及獲取縮略圖
  • 圖片、視頻列表查詢
  • 節目新建(三表關聯)
  • 節目查詢(三表關聯)

項目根據molunerfinn全棧開發實戰:用Vue2+Koa1開發完整的先後端項目(更新Koa2)教程搭建,在此感謝做者分享。java

項目結構

.
├── package.json 			// npm的依賴、項目信息文件
├── README.md 				// 說明文件
├── upload  				// 上傳文件存儲位置
├── index.js  				// Koa入口文件
├── server 					// vue-cli 生成,用於webpack監聽、構建
│   ├── config  			// 配置文件夾
│   ├── controllers 		// controller-控制器
│   ├── models 				// model-模型
│   ├── routes 				// route-路由
│   ├── schema 				// schema-數據庫表結構
└── └── utils 				// 實用工具
複製代碼

項目依賴包

如下依賴的版本都是本文所寫的時候的版本:node

  • @koa/corsv2.2.3 (跨域)python

  • koa v2.7.0mysql

  • koa-body v4.1.0 (解析post以及文件上傳)webpack

  • koa-json v2.0.2 (Koa中間件)nginx

  • koa-jwt v3.6.0 (Koa token的中間件)

  • koa-logger v3.2.1 (Koa日誌中間件)

  • koa-router v7.4.0 (Koa路由中間件)

  • mysql2 v1.6.5 (nodejs的mysql驅動)

  • sequelize v5.12.1 (操做數據庫的ORM)

爲何要使用mysql2呢?由於使用mysql時啓動項目sequelize會報Error: Please install mysql2 package manually,提示安裝mysql2

項目搭建

初始化項目

首先咱們得新建一個項目文件夾koa-demo,而後用命令行進入該文件夾,執行npm init建立項目描述文件package.json

E:\Project\WebStorm\Node\koa-demo> npm init
複製代碼

命令行裏會以交互的形式讓你填一些項目的介紹信息,依次介紹以下:(不知道怎麼填的直接回車、回車...)

  • name 項目名稱
  • version 項目的版本號
  • description 項目的描述信息
  • entry point 項目的入口文件
  • test command 項目啓動時腳本命令
  • git repository 若是你有 Git 地址,能夠將這個項目放到你的 Git 倉庫裏
  • keywords 關鍵詞
  • author 做者叫啥
  • license 項目要發行的時候須要的證書,平時玩玩忽略它

而後就能夠打開項目文件夾,能夠看到自動生成的package.json文件

入口文件

接下來咱們先加入入口文件index.js,寫入基本內容:

// index.js
import Koa from 'koa'
import koaRouter from 'koa-router'
import json from 'koa-json'
import logger from 'koa-logger'
import koaBody from 'koa-body'

const app = new Koa()
const router = koaRouter()

app.use(koaBody())
app.use(json())
app.use(logger())

app.use(async (ctx,next) => {
    await next()
})

app.on('error',(err,ctx) => {
    console.log('server error', err)
})

app.listen(3000,()=>{
  console.log('服務啓動成功,端口:3000,地址:http://localhost:3000')
})

export default app
複製代碼

而後在控制檯輸入node index.js,發現報錯,由於咱們使用了es6的import/export

Babel引入

爲了支持import/export咱們須要引入babel轉碼器

npm install @babel/core @babel/node @babel/preset-env @babel/register --save-dev
複製代碼

而後在根目錄添加.babelrc文件,寫入

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }]
  ],
  "plugins": []
}
複製代碼

而後在package.json中的scripts寫入

"scripts": {
    "server": "npx babel-node index.js" //使用 npx 省去了輸入 babel-node 完整路徑的麻煩。
    // or "server": "node_modules/.bin/babel-node index.js"
  }
複製代碼

而後運行npm run server就能夠了,能看到輸出服務啓動成功,端口:3000,地址:http://localhost:3000,則說明咱們的Koa已經啓動成功了,並在3000端口監聽。

熱啓動

用過vue-cli的都知道前端代碼修改事後,會進行熱啓動,就免去了手動重啓項目的麻煩,那麼koa2中如何進行熱啓動呢?nodemon能夠幫助咱們~

npm install nodemon
複製代碼

而後在package.json中的scripts加入

"scripts": {
    "server": "npx babel-node index.js",
    "start": "nodemon index.js --exec babel-node"
  }
複製代碼

最後運行npm run start就能夠了

斷點調試

在IDE中斷點調試須要配置一下

  1. 首先在打開菜單欄的Run->Run Configurations

  2. 而後點擊綠色+號,選擇Node.js

  3. 在右側的Configuration下面填入對應的參數

    • Node interpreter:選擇node的執行程序

    • Node parameters: 填寫參數,參數以下

      E:\Project\WebStorm\Node\koa-demo\node_modules\nodemon\bin\nodemon --exec E:\Project\WebStorm\Node\koa-demo\node_modules\.bin\babel-node

      能夠理解爲將package.json中的scripts下的start命令加入了完整路徑

    • Working directory:填寫項目目錄

    • JavaScript file:入口文件

環境搭建

項目流程圖

爲了方便理解項目結構,我作一張圖,咱們就按照這個順序進行環境搭建。

建立數據庫

MySql官網下載一個對應系統的安裝程序,安裝過程仍是比較簡單的,這裏就不詳細描述了

對於初次接觸MySql的我,使用命令操做實在有此難爲我了,還好以前裝了個可視化的工具Navicat(破解版),固然也有一些免費版的,例如:Windows上HediSQL,macOS上Sequel Pro

  1. 好了,如今咱們先建立一個鏈接koa

    • 鏈接名:koa
    • 主機名或IP地址:localhost
    • 端口:3306
    • 用戶名: root
    • 密碼:123456
  2. 而後新建一個數據庫demo

    • 數據庫名:demo
    • 字符集:``utf8 -- UTF-8 Unicode`
    • 排序規則: utf8_general_ci
  3. 最後咱們建立兩個表userresource

user表:

字段 類型 長度 主鍵 說明
id int(自增) 255 1 用戶ID
nickname varchar 50 暱稱
username varchar 50 用戶名
password varchar 128 密碼
creationTime datetime 0 建立時間
updateTime datetime 0 更新時間

resource表:

字段 類型 長度 主鍵 說明
id int(自增) 125 1 資源ID
name varchar 255 資源名稱
size double 0 資源大小
measure varchar 255 分辨率
thumbnail varchar 255 資源地址
operator varchar 255 操做者
time datetime 0 建立時間

生成數據結構

咱們須要把數據庫的表結構用sequelize-auto導出來。

更多關於sequelize-auto的使用能夠參考官方介紹或者這篇文章

由此,咱們首先全局安裝sequelize-auto

npm install -g sequelize-auto
複製代碼

進入server的目錄,執行以下語句

sequelize-auto -o "./schema" -d demo -h localhost -u root -p 3306 -x 123456 -e mysql
複製代碼
  • -o 參數後面的是輸出的文件夾目錄
  • -d 參數後面的是數據庫名
  • -h 參數後面是數據庫地址
  • -u 參數後面是數據庫用戶名
  • -p 參數後面是端口號
  • -x 參數後面是數據庫密碼,這個要根據本身的數據庫密碼來!
  • -e 參數後面指定數據庫爲mysql

而後就會在schema文件夾下自動生成兩個文件:

// user.js
/* jshint indent: 2 */
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
  id: {
    type: DataTypes.INTEGER(255),
    allowNull: false,
    primaryKey: true,
    autoIncrement: true
  },
  nickname: {
    type: DataTypes.STRING(50),
    allowNull: true
  },
  username: {
    type: DataTypes.STRING(50),
    allowNull: true
  },
  password: {
    type: DataTypes.STRING(128),
    allowNull: true
  },
  creationTime: {
    type: DataTypes.DATE,
    allowNull: true
  },
  updateTime: {
    type: DataTypes.DATE,
    allowNull: true
  }
}, {
  tableName: 'user'
});
};

複製代碼
// resource.js
/* jshint indent: 2 */

module.exports = function(sequelize, DataTypes) {
  return sequelize.define('resource', {
    id: {
      type: DataTypes.INTEGER(125),
      allowNull: false,
      primaryKey: true,
      autoIncrement: true
    },
    name: {
      type: DataTypes.STRING(255),
      allowNull: true
    },
    size: {
      type: "DOUBLE",
      allowNull: true
    },
    measure: {
      type: DataTypes.STRING(255),
      allowNull: true
    },
    thumbnailProxy: {
      type: DataTypes.STRING(255),
      allowNull: true
    },
    operator: {
      type: DataTypes.STRING(255),
      allowNull: true
    },
    time: {
      type: DataTypes.DATE,
      allowNull: true
    }
  }, {
    tableName: 'resource'
  });
};

複製代碼

鏈接數據庫

跟數據庫打交道的時候咱們都須要一個好的操做數據庫的工具,可以讓咱們用比較簡單的方法來對數據庫進行增刪改查。

我選用的是Sequelize,它支持多種關係型數據庫(SqliteMySQLPostgres等),它的操做基本都能返回一個Promise對象,這樣在Koa裏面咱們可以很方便地進行」同步」操做。

更多關於Sequelize的用法,能夠參考官方文檔,以及這幾篇文章——Sequelize中文API文檔Sequelize和MySQL對照Sequelize快速入門

安裝Sequelize依賴

npm install --save sequelize
複製代碼

而後在server目錄下的config目錄下咱們新建一個db.js,用於初始化Sequelize和數據庫的鏈接。

// config/db.js
import Sequelize from 'sequelize' // 引入sequelize

// 使用url鏈接的形式進行鏈接,注意將root: 後面的XXXX改爲本身數據庫的密碼
const demo = new Sequelize('mysql://root:123456@127.0.0.1/demo',{
    define:{
        // 取消Sequelzie自動給數據表加入時間戳(createdAt以及updatedAt)
        timestamps: false
    },
    timezone: '+08:00' // 時差區,國內須要加入否則存儲的時間會有時差
})

export default demo // 將demo暴露出接口方便Model調用
複製代碼

數據庫操做

接着咱們去models文件夾裏將數據庫和表結構文件鏈接起來。在這個文件夾下新建一個user.js的文件。

所謂增、刪、改、查,那麼咱們就先來寫一個新增用戶的操做。

// models/user.js
import demoDB from '../config/db'

// 引入user的表結構
const userModel = '../schema/user.js'

// 用sequelize的import方法引入表結構,實例化了User。
const User = demoDB.import(userModel)

// async 異步操做
const addUser = async (userInfo) => {
 await User.create(userInfo)
}

module.exports = {
  addUser
}
複製代碼

業務邏輯操做

如今咱們就須要寫一寫接收參數後的一些操做以及返回信息。

// controllers/user.js
import user from '../models/login'

const postUserInfo = async ctx => {
  const data = ctx.request.body
  const userAuth = await login.getUserByName(data.username)

  if(userAuth === null){
    let userInfo = {
      username: data.username,
      password: data.password,
      nickname: data.nickname,
      creationTime: dataTime(),
      updateTime: dataTime()
    }
    user.addUser(userInfo)
    ctx.body = {
      code: '0000',
      info: '新建成功!'
    }
  }else {
    ctx.body = {
      code: '9999',
      info: '用戶已存在!'
    }
  }
}

export default {
  postUserInfo
}
複製代碼

寫完這個還不能直接請求,由於咱們尚未定義路由,請求通過Koa找不到這個路徑是沒有反應的。

建立路由

routes文件夾下寫一個api.js的文件。

// routes/api.js
import user from '../controllers/user'
import koaRouter from 'koa-router'

const router = koaRouter()

router.post('/user',user.postUserInfo)

export default router
複製代碼

至此咱們已經接近完成咱們的第一個API了,還缺最後一步,將這個路由規則「掛載」到Koa上去。

掛載路由

爲了節約篇幅,下面省略了一些代碼,只寫了上下文做爲位置標記

// index.js
// ...
import logger from 'koa-logger'
import koaRouter from 'koa-router'
const router = koaRouter()
import api from './server/routes/api.js'
// ...
app.on('error',(err,ctx) => {
    console.log('server error', err)
})

// 掛載到koa-router上,同時會讓全部的auth的請求路徑前面加上'/auth'的請求路徑
router.use('/api', api.routes()) 
// 將路由規則掛載到Koa上。
app.use(router.routes())

app.listen(3000,()=>{
  console.log('服務啓動成功,端口:3000,地址:http://localhost:3000')
})
// ...
複製代碼

打開你的控制檯,輸入node app.js,一切運行正常沒有報錯的話,大功告成,咱們的第一個API已經構建完成!

API 測試

接口在跟前端對接以前,咱們應該先進行一遍測試,防止出現問題。

在測試接口的工具上我想postman的大名應該衆所周知了,官網下載安裝好後即可使用。

其它接口的完善

剛纔實現的不過是一個簡單的用戶新增接口,可是咱們要實現一個完整的系統demo,還須要作一些工做。

剩下的API添加,基本上只須要在modelcontrollers寫好方法,定好接口便可~

下面主要列舉一下上傳接口以及分頁查詢接口的一些知識點。

圖片上傳

在項目根目錄下咱們建立的有一個upload文件夾,上傳成功後的圖片就存儲到這裏,那麼這裏的圖片怎麼經過連接訪問呢?這就須要咱們搭建一個簡易的靜態資源服務器了。

靜態資源

這裏提供兩種方法:

  1. koa-static中間件

    npm install koa-static
    複製代碼
    // index.js
    //...
    import statics from 'koa-static'
    import path from 'path'
    
    app.use(logger())
    
    // 靜態服務
    app.use(statics(path.join(__dirname, './upload/')))
    複製代碼

    咱們在upload目錄下新建一個image文件夾,用來放圖片文件,而後掛載好後啓動服務就可使用http://localhost:3000/image/test.jpg查看圖片了。

    **注意:**將upload目錄配置爲靜態資源,那麼訪問的時候不須要輸入upload,而是直接訪問下級目錄

  2. Nginx搭建靜態資源

    這裏使用Nginx進行搭建,在官網下載穩定版本,解壓後打開conf文件夾下的nginx.conf進行修改配置

    server {
            listen       3001;
            server_name  localhost;
    
            location /upload/ {
                root E:/Project/WebStorm/Node/koa-demo/;
                autoindex on;
            }
    }
    複製代碼

    而後運行nginx.exe便可,而後咱們在upload中添加一張圖片,打開瀏覽器輸入http://localhost:3001/upload/test.jpg即可看見圖片

接口編寫

models下建立一個resource.js

// models/resource.js
import demoDB from '../config/db'
const resModel = '../schema/resource'

const Res = demoDB.import(resModel)

const postResImage = async data => {
  await Res.create(data)
}

export default {
  postResImage
}
複製代碼

主要是用來存儲圖片的一些數據到數據庫

下面咱們在controllers下建立一個resource.js

多文件須要遍歷ctx.request.files,與文件一塊兒傳過來的參數在ctx.request.body中獲取

// controllers/resource.js
import fs from 'fs'
import path from 'path'
import res from '../models/resource'
import formatTime from '../utils/formatTime'
import _res from '../utils/response'
import probe from 'probe-image-size'

const imageUrl = 'http://localhost:3000/image/'
const imagePath = path.join(__dirname,'../../upload/image')

const videoUrl = 'http://localhost:3000/video/'
const videoPath = path.join(__dirname,'../../upload/video')

const uploadImage = async ctx => {
    const file = ctx.request.files.file;

    const reader = fs.createReadStream(file.path);	// 建立可讀流

    // 獲取圖片流的尺寸,注意,這裏不能直接使用reader,否則會致使圖片損壞。
    let measure = await probe(fs.createReadStream(file.path))

    const upStream = fs.createWriteStream(`${imagePath}\\${file.name}`);		// 建立可寫流

    const data = {
      name : file.name,
      size : (file.size / 1024 / 1024).toFixed(2),
      measure : `${measure.width}*${measure.height}`,
      thumbnailProxy : `${imageUrl}${file.name}`,
      operator : 'admin',
      time : formatTime()
    }
    await res.postRes(data)
    if(!fs.existsSync(imagePath)){
      fs.mkdir(imagePath,err => {
        if(err) throw err
        reader.pipe(upStream)	// 可讀流經過管道寫入可寫流
        return ctx.body = _res.success('上傳成功')
      })
    }else {
      reader.pipe(upStream)	// 可讀流經過管道寫入可寫流
      return ctx.body = _res.success('上傳成功')
    }
}

export default {
  uploadImage
}
複製代碼

接收到圖片後,再想獲取圖片的尺寸須要npm install probe-image-size,剛開始的時候一直在image-size上折騰,真是搞了很久,一直覺得是本身哪裏用法不對。後來才發現是對流形式的不支持,就換到了probe-image-size.

最後在routes添加接口

// router/api.js
import koaRouter from 'koa-router'
const router = koaRouter()
import resource from '../controllers/resource'

router.post('/upload/image',resource.uploadImage)

export default router
複製代碼

獲取視頻縮略圖

上傳視頻後在咱們通常都須要獲取視頻的縮略圖,用來在前端列表中展現。

咱們使用FFmpeg,一個領先的多媒體框架。

首先在官網下載對應平臺的包。我這裏使用的是windows,下載完成後將FFmpeg解壓到D:\ffmpeg下。

並配置好系統的環境變量,添加D:\ffmpeg\bin到系統變量。詳細點擊查看

若是設置環境變量無效的話,還能夠手動設置ffpemg的位置。

FFMpeg.setFfmpegPath('D:/ffmpeg/bin/ffmpeg.exe')

而後在項目目錄安裝node的中間件fluent-ffmpeg

npm install fluent-ffmpeg
複製代碼

而後就可使用了。

import FFMpeg from 'fluent-ffmpeg'
FFMpeg.setFfmpegPath('D:/ffmpeg/bin/ffmpeg.exe')

const screenshots = function(fileName){
  FFMpeg('upload/video/'+ fileName)
    .screenshots({
      timemarks: ['0.5'],
      filename: 'thumbnail-%b.png',
      count: 1,
      folder: 'upload/video'
    })
}

export default {
  screenshots
}
複製代碼

其它操做請參考官方文檔

分頁查詢

這裏的查詢主要是查詢上傳的圖片的信息,返回給前端進行列表展現。

因此接口與上傳接口同在resource.js文件中。

// models/resource.js
// ...省略
const getResImage = async (data) => {
  const { pageNo, pageSize } = data
   return await Res.findAndCountAll({
      limit: parseInt(pageSize),
      offset: (parseInt(pageNo)-1) * parseInt(pageSize),
    }).then(result=>{
      return result
    })
}

export default {
  getResImage,
  postResImage
}
複製代碼
// controllers/resource.js
// ...省略
const getResImageList = async ctx => {
  const data = ctx.query

  const list = await res.getResImage(data)
  const _resData = {
    pages:{
      total: list.count
    },
    sources: list.rows
  }
  ctx.body = _res.success('成功',_resData)
}


export default {
  uploadFile,
  getResImageList
}
複製代碼
// router/api.js
router.get('/resource/image',resource.getResImageList)
複製代碼

至此,KOA2中實現RESTFul 風格的API就算完成了。

一對多的多表分頁查詢時會在子查詢裏中分頁,可以使用subQuery:true。詳見項目中節目查詢的代碼。

總結

最後將本文的項目代碼放至了Github,若是這個項目對你有幫助,但願你們能夠fork,給我提建議,若是再有時間,能夠點個Star那就更好啦~

參考文獻

相關文章
相關標籤/搜索