likes
comments
collection
share

koa服务端项目搭建

作者站长头像
站长
· 阅读数 11

Koa 是一个基于 Node.js 的 Web 开发框架,它的目标是让 Web 开发更简单、更有表现力和更健壮。Koa 借鉴了 Express 框架的中间件的精髓,提供了更加简洁和优雅的代码风格,支持 async/await 等新型语法。

如果文章中有什么介绍的不清楚的地方,可以访问 本项目github地址 通过看完整的项目代码结合本文进行阅读

一、项目初始化

1.安装koa

yarn koa

2.创建app.js

import Koa from 'koa'

const app = new Koa()

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

// 监听端口
const server = app.listen(3000,'127.0.0.1',()=>{
  const address = server.address()
  console.log(`服务启动成功!请访问${address.address}:${address.port}`);
})

3.控制台启动服务

在项目根目录下打开控制台输入命令 node app.js 打印出服务信息代表服务启动成功

二、压缩编译代码

1.安装webpack

yarn add -D webpck webpack-cli webpack-node-externals

其中webpack-node-externals是用于在 Webpack 的 Node.js 环境中排除 Node.js 内置模块和已安装的第三方模块,以减小打包文件的体积,减轻浏览器的加载负担。

2.安装babel插件

yarn add -D @babel/core @babel/node @babel/preset-env babel-loader

babel插件用于将es6代码编译成es5代码。 在根目录下创建.babelrc文件,它是 Babel 的配置文件,可以用于指定 Babel 的转换规则和插件等,

3.配置webpack

创建webpack.config.js

const nodeExternals = require("webpack-node-externals")
module.exports = {
  // target 设置为 node,webpack 将在类 Node.js 环境编译代码,使用 Node.js 的 require 加载 chunk,而不加载任何内置模块,如 fs 或 path
  target:"node",
  // 打包入口
  entry:"./app.js",
  // 输出文件
  output:{
    filename:"app.js",
    // 清除之前打包的文件
    clean:true,
  },
  externals:[nodeExternals()],
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
}

4.配置package.json

在package.json中加入下面几行代码,用于定义项目中常用的命令,其中我们使用nodomon替换了node,nodemon 是一个基于 Node.js 开发的工具,使用 nodemon 可以让我们在修改文件后,自动重启 Node.js 应用,从而省去手动重启的麻烦。如果没有安装nodemon,可以使用sudo yarn -g nodemon全局安装一下。

 "scripts": {
    "dev": "nodemon --exec babel-node ./app.js",
    "build": "npx webpack build --mode=production"
  },

yarn dev是开发时启动的命令,在控制台执行后会在我们本地启动服务

yarn build是打包编译的代码,执行build后会自动创建出一个dist目录。目录中的文件就是编译后的代码。

三、创建项目目录结构

在项目的根目录下创建下面的文件夹,用于拆分细化项目的各个功能及模块


    ├── config             // 配置文件目录
    ├── controller         // 控制器,也就是业务逻辑代码,路由接受到请求后处理的代码
    ├── core               // 核心代码
    ├── middlewares        // 自定义中间件目录
    ├── public             // 存放静态文件的目录
    ├── router             // 路由
    ├── models             // 数据模型
    ├── utils              // 工具集
    ├── services           // 该目录存放对数据模型的操作的代码
    ├── validators         // 校验前端传过来数据的代码
    

四、中间件

1.koa-static

使用yarn add koa-static将koa-static添加到项目中,koa-static 是一个 Koa 框架的中间件,用于提供静态文件服务,在app.js文件中加入以下代码,就可以实现通过URL对pulic目录下的静态资源的访问,例如public目录下有一个index.html文件,那么我们在本地启动服务后就可以通过http://localhost:3000/index.html访问到这个文件。

import KoaStatic from 'koa-static'
// 静态资源访问
app.use(KoaStatic(path.join(path.resolve(),'/public')))

2.koa-router

使用yarn add @koa/router将@koa/router添加到项目中

假如我们要创建用户的接口,在router目录中创建user.js,并添加以下代码

import Router from '@koa/router'

// 创建路由对象
const router = new Router({
  // 路由前缀
  prefix:"/user"
})

router.get("/info",(ctx,next)=>{
    ctx.body = "用户信息"
})

export default router

在app.js中加入

import userRouter from './router/user'
app.use(userRouter)

这样我们就能通过 http://localhost:3000/user/info 访问到这个接口

3.koa-body

使用yarn add koa-body将koa-body添加到项目中,用于解析 HTTP 请求主体,可以从提交的表单中提取文件和任何其他数据。可以使用 koa-body 来处理各种 HTTP 请求,例如 POSTPUT 和 PATCH 等请求。koa-body 可以解析 JSON,UrlencodedMultipartyForm 和 Text 等多种请求主体格式,可以根据需要进行配置。如果只需要解析简单的表单数据和JSON数据,可以使用KoaBodyParser;如果需要处理更加复杂的请求体,包括文件上传等,就应该选择KoaBody。

在app.js中加入

import KoaBody from 'koa-body';
// 接受请求参数
// app.use(KoaBodyparser())
app.use(KoaBody())

在router/user.js目录中加入

router.post("/create",(ctx,next)=>{
    // 打印post参数
    console.log(ctx.request.body)
})

也可以通过它实现文件上传代码如下

// 文件上传
router.post("/upload",koaBody({
  multipart: true,
  formidable:{
    // 限制字段​​数
    maxFields:1,
    // 上传文件目录
    uploadDir: path.join(path.resolve(),'/public/upload'),
    // 最大的文件大小
    maxFileSize:1*1024*1024,
    // 包含源文件拓展
    keepExtensions: true,
    // 是否支持上传多个文件,默认为true
    multiples:false,
    // 自定义文件名
    filename:(name,suffix)=>`${Date.now()}-${name}${suffix}`
  }
}),(ctx,next)=>{
  ctx.body={
    code:200,
    data:ctx.request.files,
    message:"上传成功"
  }
})

4.koa-log4

使用yarn add koa-log4将koa-log4添加到项目中,用于记录 HTTP 请求和响应信息,可以方便地在开发和生产过程中进行调试和日志分析。它是基于 log4js 模块构建的。具体用法

5.koa-bouncer

使用yarn add koa-bouncer将koa-bouncer添加到项目中,用于验证和转换 Koa 应用程序中的请求数据的中间件。它提供了一组简单的验证器,可以轻松地验证 JSON 和 表单数据,也可以将数据解析成指定的数据类型。具体用法

6.koa-onerror

使用yarn add koa-onerror将koa-bouncer添加到项目中,koa-onerror 是一个 Koa 框架中间件,用于处理程序的错误和异常。它可以捕获 Koa 应用程序的错误和异常并自动处理,展示友好的错误信息和提示,帮助开发人员更快地定位问题。

7.koa-session

使用yarn add koa-session将koa-session添加到项目中,用于在服务端存储和管理会话(session)数据。会话是一种跨请求的数据保存技术,它使得我们在多个请求之间共享数据变得更加简单。通过使用 koa-session 中间件,可以在服务端为每个客户端创建一个cookie存储服务端(session)数据的sid,服务端(session)中存储需要共享的数据。客户端会自动在后续请求中携带这个cookie,以便服务端通过这个cookie中的sid检索出对应的session的数据,既可以通过它存储数据也可以用来登录鉴权

7.1 session登录鉴权

在根目录下app.js添加下面代码:

import KoaSession from 'koa-session';
// 对cookie中的sid进行加密签名,xxxx可以替换成你自己定义的字符串
app.keys = ['xxxx']
// session 配置
const SESSION_CONFIG = {
  key:"key", // 设置cookie的key的名字
  maxAge:1000*60*60*12, // 设置有效期为12个小时
  httpOnly: true, // 仅服务端修改
  signed:true,// 签名cookie
}
app.use(KoaSession(SESSION_CONFIG,app))

然后再写一个登录接口,代码如下:

// 登录-session
router.post("/login",(ctx,next)=>{
    // 获取请求参数
  const {username,password} = ctx.request.body
  // 校验用户名密码
  if(body.username=="TC"&&body.password=="123456"){
    ctx.session.isLogin = true
    ctx.session.username = body.username
    ctx.body={
      code:200,
      message:"登录成功"
    }
  }else{
    ctx.body={
      code:400,
      message:"登录失败!用户名或密码错误"
    }
    ctx.status = 400
  }
})

这样就成功登录成功了,那如何鉴权呢?还需要继续封装一个鉴权的中间件,代码如下:

// 不需要鉴权的白名单接口
const whiteApis = ['/login']
// 鉴权中间件
const sessionAuth = async (cxt,next)=>{
  const {path,method} = cxt.request
  // 判断接口是否需要鉴权
  const pass =whiteApis.includes(path)
  if(pass){ // 如果不需要直接向下执行
    await next()
  }else{ // 如果需要,判断是否登录
    if(cxt.session.isLogin){ // 如果登录继续向下执行
      await next()
    }else{ // 如果没登录,直接返回鉴权失败信息
      cxt.body = {
        code:401,
        message:"用户未登录"
      }
    }
  }
}

鉴权中间加写好了,我们直接router.use(sessionAuth),这样就可以使用了,但是注意一定要放在所有需要鉴权接口的前面,这样才能先走鉴权中间件,鉴权完毕才能调用鉴权接口。session的登录鉴权就OK了,还有一种token鉴权方式会在下面的内容里介绍。

7.2 svg-captcha生成验证码

使用yarn add svg-captcha将svg-captcha添加到项目中,它可以轻松生成验证码并返回 SVG 格式的图片。还可以生成算数计算的验证码。具体实现请查看 svg-captcha中文文档。 下面我们就简单使用一下。代码如下:

// 验证码接口
router.get("/vercode",(cxt,next)=>{
  let option = {
    size:4, // 验证码长度
    ignoreChars: '0o1i', // 验证码字符中排除 0o1i
    noise: 6, // 干扰线条的数量
    color: true, // 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有
    // background: '#cc9966', // 验证码图片背景颜色
    // charPreset:"23456789"  // 预设字符,二维码只能从预设的字符中生成
  }
  // 生成简单验证码
  var captcha = svgCaptcha.create(option)
  // 将验证码结果字符串存入session中
  cxt.session.vercode = captcha.text
  cxt.type = "svg"
  cxt.body=captcha.data
})

这样我们就可以在浏览器地址栏里输入 http://localhost:3000/vercode 就可以看到一张验证码,每次请求都会重新生成一张,在登录接口里。我们可以通过cxt.session.vercode取出验证码与接口请求验证码参数对比一下看是否相等

五、MongoDB数据库使用

我们这里使用的数据库是MongoDB数据库,只需要创建一个数据库就可以了,无须手动一个一个的创建表。项目中我们使用mongoose工具来操作是数据库,mongoose 是一个 Node.js 应用程序的对象模型工具,它使得在 MongoDB 中的数据管理变得更加容易和灵活。它提供了一种类似于面向对象语言的方式来管理数据,同时也包含了非常强大的查询和更新功能。mongoose的中文文档在这 mongoose中文网

1.数据库连接

具体代码如下:

import mongoose from 'mongoose';
import config from '../config/index' //引入项目配置文件
// config.user 是MongoDB数据库的用户名
// config.password 是MongoDB数据库的用户密码
// config.ip 是MongoDB数据库的所在的服务器ip,如果数据库本地就用localhost
// config.port 是MongoDB数据库的端口号,一般都是27017
// config.name 是MongoDB数据库的数据库名称
 // 数据库连接 mongoose.connect(`mongodb://${config.user}:${config.password}@${config.ip}:${config.port}/${config.name}`,{
    // 是否在连接过程中使用新的 URL 解析器
    useNewUrlParser:true,
    // 先从admin表中验证数据库用户身份才行
    authSource:"admin",
    // 表示是否使用新的、基于 Node.js 的拓扑结构监视引擎
    useUnifiedTopology:true,
  })
  mongoose.connection.on("error",(err)=>{
    // 打印错误信息
    console.log(err)
  })
  mongoose.connection.on("open",(err)=>{
    console.log("数据库连接成功")
  })
  
  

2.创建数据模型

例如我们要创建一个用户文档的数据模型,就在mondels文件中创建一个user.js文件,bcrypt可以将用户的密码进行加密后再保存进数据库中,具体使用请查看 brypt使用,代码如下:

import {Schema,model} from 'mongoose'
import bcrypt from 'bcrypt'

 const schema = new Schema({
  username:{
    type:String,
    required:[true,'用户名为必传参数'],
    unique: true, //用户名不能重复
    // 去掉两头空格
    trim:true
  },
  password:{
    type:String,
    required:[true,'密码为必传参数'],
    // 查询时不返回
    select: false,
    trim:true
  },
 phone:{
    type:String,
    required:true, 
    validate: {
        validator: function(v) {
            return /\d{3}-\d{3}-\d{4}/.test(v); 
        },
        message: props => `${props.value} 格式错误` 
     },
     required: [true, '手机号必传'],
  },
})

// 密码加密
schema.pre('save',function(next){
  var user = this;
  //产生密码hash当密码无更改的时候
  if (!user.isModified('password')) return next();
  // 产生一个盐值(salt)
  bcrypt.genSalt(10, function(err, salt) {
    if (err) return next(err);
    // 使用盐值(salt)和明文密码进行哈希加密处理
    bcrypt.hash(user.password, salt, function(err, hash) {
    if (err) return next(err);
    // 使用hash覆盖明文密码
    user.password = hash;
    next();
    });
  });
})

// 密码对比校验
schema.method.comparePass = function (password,callback){
  bcrypt.compare(password,this.password,(isMatch)=>{
    if(err) return callback(err)
    callback(null,isMatch)
  })
}
export default model("User",schema)

3.数据库操作

在services文件夹中创建一个user.js,代码如下:

import User from "../modals/user"

// 创建用户
const create=async ({username,phone,password})=>{
  const res = await User.create({
      username,
      phone,
      password
  })
  // 
  return res.id
}

// 根据id查找某一用户
const findOneById=async (id)=>{
    return await User.findById(id);
}

// 获取用户列表
const findAll=async ({current=1,pageSize=10,...other})=>{
  // current 当前页码默认为1
  // pageSize 一页数据条数
  // other 其他查询参数
  return await User.find(other) // 匹配特定条件的文档 
  .skip((current - 1) * pageSize) // 跳过前面的文档数量 
  .limit(pageSize) // 每页的文档数量 
}

export defult{
    create,
    findOneById,
    findAll
}