koa2初尝试

Douglas(s)Angelo 发布于1月前
0 条问题

原文首发于我的 博客 ,欢迎点击查看获得更好的阅读体验~

前言

一直都想接触下服务端的内容,以前看过python基础,但是因为在工作中基本上使用不到,所以基础看了几遍没有实际项目操作就忘记了,忘~记~了~

朝三暮四后最终选择了基于Nodejs的中间件Koa2完成了一个简单的RESTful风格的API项目。

项目中主要完成了以下API接口:

  • 登录
  • 用户的增删改查
  • 图片上传、视频上传以及获取缩略图
  • 图片、视频列表查询
  • 节目新建(三表关联)
  • 节目查询(三表关联)

项目根据 molunerfinn 的 全栈开发实战:用Vue2+Koa1开发完整的前后端项目(更新Koa2) 教程搭建,在此感谢作者分享。

项目结构

.
├── package.json             // npm的依赖、项目信息文件
├── README.md                 // 说明文件
├── upload                  // 上传文件存储位置
├── index.js                  // Koa入口文件
├── server                     // vue-cli 生成,用于webpack监听、构建
│   ├── config              // 配置文件夹
│   ├── controllers         // controller-控制器
│   ├── models                 // model-模型
│   ├── routes                 // route-路由
│   ├── schema                 // schema-数据库表结构
└── └── utils                 // 实用工具

项目依赖包

以下依赖的版本都是本文所写的时候的版本:

@koa/cors
koa
koa-body
koa-json
koa-jwt
koa-logger
koa-router
mysql2
sequelize

为什么要使用 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 :入口文件

环境搭建

项目流程图

为了方便理解项目结构,我做一张图,我们就按照这个顺序进行环境搭建。

koa2初尝试

创建数据库

去 MySql 官网 下载一个对应系统的安装程序,安装过程还是比较简单的,这里就不详细描述了

对于初次接触 MySql 的我,使用命令操作实在有此难为我了,还好之前装了个可视化的工具 Navicat (破解版),当然也有一些免费版的,例如:Windows上 HediSQL ,macOS上 Sequel Pro

  1. 好了,现在我们先创建一个连接 koa

    koa
    localhost
    3306
    root
    123456
  2. 然后新建一个数据库 demo

    demo
    `utf8 -- UTF-8 Unicode
    utf8_general_ci
  3. 最后我们创建两个表 user 与 resource

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

然后就会在 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 ,它支持多种关系型数据库( Sqlite 、 MySQL 、 Postgres 等),它的操作基本都能返回一个 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添加,基本上只需要在 model 和 controllers 写好方法,定好接口即可~

下面主要列举一下 上传接口 以及 分页查询接口 的一些知识点。

图片上传

在项目根目录下我们创建的有一个 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 到系统变量。详细 点击查看

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那就更好啦~

参考文献

查看原文: koa2初尝试

  • smallelephant
  • silvermouse
  • biggorilla
  • blackelephant
  • ticklishmouse
  • blackfish
  • brownostrich
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。