koa2搭建一个自己服务器

322 阅读12分钟

前言:最近学习了用户koa2搭建webapi框架,闲来无事,想自己动手试一下,本文纯属自己的理解,如有错误,欢迎大家斧正。

  1. npm初始化

首先对于一个项目要进行一个版本管理的文件,所以我们进行npm 初始化,用来进行我们的版本管理

npm init -y 命令执行结束后会生成一个package.json的文件,这个文件就是我们的项目管理文件,用来记录项目的依赖的版本。

  1. 安装koa2框架

接下来就是要搭建我们自己项目,同样我们需要安装koa2框架。这是一个轻量级框架首先先把koa工具包安装 npm install koa 命令进行

如果遇到安装失败可以试试管理员身份启动再进行一个安装。

切记不要用koa进行项目文件夹命名

下载好后会出现两个文件一个叫做node_modules,另一个叫做package-lock.json

  • node_modules:用来记录项目的依赖
  • package-lock.json:用来锁定版本的最终依赖版本
  1. 入口文件 main.js

作为一个项目肯定要有一个入口文件 所以创建main.js,我们首先创建一个src文件夹用来存放我们的项目代码,在src中创建一个main.js这个文件就是我们项目的入口文件。项目的入口文件首先考虑所有的请求首先通过这个文件,所以我们在这个文件中创建一个服务器,利用koa去创建,首先通过require方式引入这个包并且创建一个服务器。

//导入 koa依赖

const Kos = require('koa')


//进行实例化

const app = new Koa();


//中间件

app.use((ctx,next) => {

    // 参数中  ctx 是所有的请求 和 请求体 next 代表下一步

    ctx.body = 'hello world'

})

ctx是context的缩写中文一般叫成上下文,这个在所有语言里都有的名词,可以理解为上(request)下(response)沟通的环境,所以koa中把他们两都封装进了ctx对象,koa官方文档里的解释是为了调用方便,ctx.req=ctx.request,ctx.res=ctx.response,类似linux系统中的软连接?最终执行还是request和response对象。

body是http协议中的响应体,header是指响应头

ctx.body = ctx.res.body = ctx.response.body

  1. 启动服务器

创建好服务器之后,让我们马上测试一个服务器是否可以正常启动,这时候我们来配置一下服务器运行的端口号:

app.listen(3000,()=>{

    // 表示服务器将在3000端口号运行

    console.log('服务器已经启动')

})

执行命令,启动我们的服务器

可以看见服务器是启动成功的,body的配置也是正常的,在这里如果我们修改ctx.body的内容在网页却不能实时性刷新,这是因为ctx.body是长内存,如果想要实时显示,那么就要重启服务,重新获取,所以为了能够做到实时性刷新内容,当main.js文件发生改变时自动重启服务,我们安装一个nodemon工具(会监听文件的改变),同样 安装命令是 npm install nodemon -D下载好工具之后,我们需要用工具去启动项目,所以我们修改package.json中的scripts,添加命令执行命令:

"scripts": {

    // 用来重启这个文件 也就是我们的服务器

    "dev":"nodemon ./src/main.js",

    "test": "echo "Error: no test specified" && exit 1"

  },

ok,重启我们的服务 ,运行 npm run dev ,这时重写ctx.body的内容会发现网页的内容是实时性的改变。

  1. 项目整体配置的 .env文件

同样,一个项目需要很多的配置文件,我们的配置文件相当于是一个模块,应该单独的抽离出,所以我们建立一个.env的文件去存储我们的项目配置,在根目录下新建一个.env文件记录我们项目的配置项,同时下载一个dotenv工具(可以去根目录下加载 .env 文件),下载还是 npm install dotenv,配置项目中先把我们的端口号修改,也就是在.env文件中写入:

APP_PORT = 8000 // 记录服务器启动的端口

OK,现在管理工具和配置文件夹已经准备好,可以愉快的进行配置的管理了,在src下新建一个config.default.js文件,用来引入已经配置好的.env文件内容:

const dotenv = require('dotenv');


dotenv.config()


//console.log(process.env);

module.exports = process.env

我们将process.env中的内容打印一下:

{

  ALLUSERSPROFILE: 'C:\ProgramData',       

  APPDATA: 'C:\Users\dell\AppData\Roaming',

  APP_PORT: '8000',

  'asl.log': 'Destination=file',

  CHROME_CRASHPAD_PIPE_NAME: '\\.\pipe\crashpad_16480_POSRUSFSUESNEXRD',

  CLASSPATH: '.;C:\Program Files\Java\jdk1.8.0_221\lib',

  COLORTERM: 'truecolor',

  .......

  //这里就看一下,其他的用到可以看一下

}

显然我们的配置是已经拿到了,接下来就可以在main.js中更改我们的配置:

app.listen(APP_PORT,()=>{

    console.log('服务器已经启动')

}) 

我们把对应的配置信息按照解构的方式 加入到main.js中,接下来需要什么配置就可以自行补充,后面我们会补充数据库的配置。

  1. 路由编写

对于不同的请求我们需要不同的函数去做相应处理,所以我们编写路由去处理不同的请求,同样我们需要安装一个工具叫做 koa-router,依旧是我们熟悉的npm install koa-router,安装好路由之后我们做一些相应的配置文件,在src下建立相对的router文件夹用来处理不同模块的请求,我们创建一个users.route.js的文件用来编写我们的user相对应的处理函数:

在users.route.js中我们需要依次进行:

  • 导入工具包
  • 实例化对象
  • 路由的编写
  • 中间件的注册

前两项不用做过多的解释,第三项是建立这个模块最初的目的,那么注册中间件又是什么呢?后面会介绍。

// router文件夹

const Router = require('koa-router');

 //路由前缀

const router = new Router({prefix:'/users'});


router.post('/add',(ctx,next)=>{

    //.......操作

})

router.post('/login', (ctx,next)=>{

    //.......操作

})


module.exports = router;
// main.js 文件

const {APP_PORT} = require('./config/config.default')

const app = require('./app/index.js');

app.listen(APP_PORT,()=>{

    console.log('服务器已经启动')

}) 

我们既然编写好了路由并且也对main.js进行了改造,那么接下来继续优化我们的目录结构。我们创建一个app的文件用来存放我们的初始化内容也就是将koa有关的代码抽离,在main.js中只进行引入。ok,完成之后接下来就是将路由和控制器进行拆分让每个文件只完成对应的操作:

路由:路由就是用来解析url地址,根据不同的地址分配给不同的控制器去完成数据的处理

控制器:用来处理不同路由下的逻辑业务

那么就是对users.route.js进行改写,将其中的控制层抽离出。

const Router = require('koa-router');

const {add,login}  = require('../controller/user.controller')


 //路由前缀

const router = new Router({prefix:'/users'});


router.post('/add',add)

router.post('/login',login)


// console.log(router);


module.exports = router;

改写好的我们的路由文件,路由控制器文件文件如下:

class UserController {

    async add(ctx, next) {

        ctx.body = "这是用户添加方法"

    }

    async login(ctx, next) {

        ctx.body = "这是用户登录方法"

    }

}

module.exports = new UserController()

OK,既然把路由和控制器拆分开以后,我们现在考虑请求体,我们不能只是单纯的将数据发送给请求用户,更要知道请求用户的请求参数,所以我们对于请求体做解析,先安装解析依赖 koa-body

同样 npm install koa-body,安装好之后,我们需要对body进行解析,因为之前已经将koa单独抽离,所以我们现在在app下的index.js中改写就好:

const Koa = require('koa');

const KoaBody =require('koa-body')

const usersRouter = require('../router/users.route')

const app = new Koa();

// console.log(KoaBody);

app.use(KoaBody()).use(usersRouter.routes())

module.exports = app;

ok ,现在我们已经把 koa-body注册成为了中间件,但是注意,在注册koa-body之前要注册在最前面,否则不生效,既然已经能够将body进行解析,那我们可以解析请求数据测试一番,因为我们用的post请求,所以使用postman测试一下:

大家可以自己试一下,我这里可以成功,但是注意postman要把请求换成json格式

  1. 改写controll,拆分 serve

我们继续改写controller层,将我们的serve层拆分,也就是model层,用来单独处理我们的数据库部分,建立 service 文件夹 新建 user.serve.js模块 编写我们的代码:

class UserService{

    async createUser(user_name,password){

        return '添加成员成功';

    }

}

module.exports = new UserService()

service下的模块搭建完成,同时把cotroller下的模块代码替换

const {createUser} = require ( '../service/user.serve')

class UserController {

    async add(ctx, next) {

        // ctx.body = "这是用户添加方法"

        const {username,password} = ctx.requedt.body;

        const res = await createUser(username,password); 

    }

    async login(ctx, next) {

        ctx.body = "这是用户登录方法"

    }

}



module.exports = new UserController()

OK,距离我们搭建好框架又完成了一步,现在我们的service层编写完成,那接下来就是将我们的项目和数据库能关联起来。

  1. 关联数据库

还是安装工具 sequelize ORM 这是一个数据库工具

ORM:对象关系映射,在这是将数据表中的数据映射成一个对象

执行 npm install mysql2 sequelize,安装好之后,我们进行数据库的连接,在src下新建db文件夹, 新建 seq.js文件 同时对数据库进行连接测试

const {Sequelize} = require('sequelize');

//将我们的项目配置的环境变量导入

const {MYSQL_HOST,MYSQL_PORT,MYSQL_USER,MYSQL_PWD,MYSQL_DB} = require('../config/config.default.js');

//进行数据库的连接

const seq = new Sequelize(MYSQL_DB,MYSQL_USER,MYSQL_PWD,{

    host:MYSQL_HOST,

  dialect: 'mysql'

});


//测试一下我们的数据库是否连接成功

seq.authenticate().then(()=>{

    console.log('数据库链接成功')

}).catch((err)=>{

    console.log('数据库链接失败',err)

})


module.exports = seq;

在这里我们看到已经把关于数据库的配置也放在了.env文件下,完成seq配置以后,我们新建model文件夹,用来维护我们的数据表

const { DataTypes } = require ( 'sequelize');

const seq = require('../db/seq');

const User = seq.define(

    'usersInfo',

    {

        username:{

            type:DataTypes.STRING,

            allowNull:false,

            unique:true,

            comment:'用户名是唯一的'

        },

        password:{

            type:DataTypes.CHAR(64),

            allowNull:false,

            comment:'密码'

        },

        isAdmin:{

            type:DataTypes.BOOLEAN,

            allowNull:false,

            defaultValue:0,

            comment:'0 代表非管理员 1代表是管理员'

        }

    }

)

//用来初始化数据表 如果之前存在就强制删除 

User.sync({force:true})



module.exports = User;

这里我们将数据表新建完成,接下来就是对数据的管理,我们继续改写我们的代码,回到我们的service,讲新建用户继续完善:

const User = require('../model/user.model');

class UserService{

    async createUser(user_name,password){

        // 数据库操作都是异步操作,我们需要等数据返回

        const res = await User.create({username:user_name,password});

        // 将结果进行返回

        return res.dataValues;

    }

}

module.exports = new UserService()

service改写完成后,在cotroller中我们需要将我们的数据库操作结果进行返回,所以我们继续改写我们的cotroller层代码:

const {createUser} = require ( '../service/user.serve')

class UserController {

    async add(ctx, next) {

        // ctx.body = "这是用户添加方法"

        const {username,password} = ctx.requedt.body;

        const res = await createUser(username,password); 

        ctx.body = {

            code:200,

            message:'添加用户成功',

            result:{

                id:res.id,

                username:res.username

            }

        }

    }

    async login(ctx, next) {

        ctx.body = "这是用户登录方法"

    }

}

module.exports = new UserController()

ok,目前已经完成了大部分工作,接下来就是逻辑的完善,让我们继续改写

const {createUser,getUserInfo} = require ( '../service/user.serve')

class UserController {

    async add(ctx, next) {

        // ctx.body = "这是用户添加方法"
 
        const {username,password} = ctx.requedt.body;

        //用来判断账号密码的合法性....这里做简单处理

        if(!username||!password){

            ctx.status = 400,

            ctx.body = {

                code:10001,

                message:'用户名或者密码为空',

                result:''

            }

            return;

        }

        //判断用户是否已经曾在

        const isHasUser = await getUserInfo({username});

        if(isHasUser){

            ctx.body = {

                code:10002,

                message:'用户已经存在,请重新申请',

                result:''

            }

            return;

        }

        //基本的判断完成后进行用户的添加

        const res = await createUser(username,password); 

        ctx.body = {

            code:200,

            message:'添加用户成功',

            result:{

                id:res.id,

                username:res.username

            }

        }

    }

    async login(ctx, next) {

        ctx.body = "这是用户登录方法"

    }

}

module.exports = new UserController()

我们在cotroller中加入一些基本的逻辑判断,同时将service也进行改写

const User = require('../model/user.model');

class UserService{

    async createUser(user_name,password){

        // 数据库操作都是异步操作,我们需要等数据返回

        const res = await User.create({username:user_name,password});

        // 将结果进行返回

        return res.dataValues;

    }

    async getUserInfo({id,username,password,isadmin}){

        //用户来定义搜索条件

        const whereOpt = {}

        id && Object.assign(whereOpt,{id})

        username && Object.assign(whereOpt,{username})

        password && Object.assign(whereOpt,{password})

        isadmin && Object.assign(whereOpt,{isadmin})


        const res = await User.findOne({

            attributes:['id','username','password','isAdmin'],

            where:whereOpt,

        })

        return res ? res.dataValues : null;

    }

}


module.exports = new UserService()

到目前为止,我们整个逻辑已经跑通,哪还有可以继续优化结构的地方吗?答案是肯定的,我们会在controller中做一些逻辑处理,比如登录时判断账号的用户密码是否为空......这些逻辑是否可以单独处理,抽离出来当做中间件呢?答案也是肯定的,那让我们继续优化,在src下建立middleware文件夹来处理

const {getUserInfo} = require('../service/user.serve')

const userValidator = async (ctx ,next)=> {

    const {username,password} = ctx.request.body;

    if(!username||!password){

        ctx.status = 400,

        ctx.body = {

            code:10001,

            message:'用户名或者密码为空',

            result:''

        }

        return;

    }

    await next();

}

const userIsHasValidator = async ( ctx,next)=>{

    const { username} = ctx.request.body;

    const isHasUser = getUserInfo({username});

    if(isHasUser){

        ctx.body = {

            code:10002,

            message:'用户已经存在,请重新申请',

            result:''

        }

        return;

    }

    await next();

}


module.exports = {

    userValidator,

    userIsHasValidator

}

ok ,现在已经将一些逻辑处理的中间件进行了抽离,接下来就是在路由处理时先进性中间件的判断,我们改写router下的代码

const Router = require('koa-router');

const {add,login}  = require('../controller/user.controller')

const {    userValidator,userIsHasValidator } = require ('../middleware/index')

 //路由前缀

const router = new Router({prefix:'/users'});

router.post('/add',userValidator,userIsHasValidator,add)

router.post('/login',login)



// console.log(router);



module.exports = router;

好的,现在工作已经完成,我们利用postman去检测一下是否可以添加成功:

数据库中的信息呢?当然也是成功的

res {

  isAdmin: false,

  id: 2,

  username: 'mateng',

  password: '1234',

  updatedAt: 2022-04-27T06:36:40.022Z,

  createdAt: 2022-04-27T06:36:40.022Z

}

OK,是可以的,那么我们整体的结构就已经OK了,那么其实还有一个问题就是跨域,怎么解决呢?其实在koa2中也进行了了配置项,我们安装@koa/cors ,同样是用npm进行安装,那么我们要在那里改写呢,当然是在路由进行切换时候进行检测,也就是我们的app.js中:

const Koa = require('koa');

const cors = require('@koa/cors')

const KoaBody =require('koa-body')

const usersRouter = require('../router/users.route')

const app = new Koa();


// console.log(KoaBody);

app.use(KoaBody()).use(cors()).use(usersRouter.routes())



module.exports = app;

总结

所以我们通过koa2能够自己搭建一个小的服务端框架,一起回顾一下我们都干了啥:

  1. npm 初始化
  2. 安装koa2框架
  3. 编写入口文件
  4. 编写.env项目配置文件
  5. 路由拆分
  6. 控制器拆分
  7. serve拆分
  8. 数据库连接
  9. 数据表建立
  10. 进行请求

按照这几部大家可以搭建一个属于自己的小框架,快动手试试吧。