基于KOA框架的api开发

一、项目初始化

1、创建项目依赖文件

npm init -y ,生成 pageage.json 文件,用于记录项目的依赖

2、创建git本地仓库

git init,生成 .git隐藏文件,git的本地仓库

二、搭建项目

1、安装koa框架

npm i koa

2、编写最基本的app

创建src/main.js

const Koa = require('koa')
const app = new Koa()
​
// 中间件
app.use((ctx,next)=>{
    ctx.body = 'hello Koa'
})
​
// 监听端口
app.listen(3000,()=>{
    console.log('server is running at http://localhost:3000')
})

三、项目基本优化

1、自动重启服务

安装nodemon工具,npm i nodemon -D

编写package.json脚本,执行npm run dev 启动服务

{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon ./src/main.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "koa": "^2.15.3",
  },
  "devDependencies": {
    "nodemon": "^3.1.4"
  }
}
​
2、读取配置文件

安装dotenv,在应用程序中,可以使用dotenv库来读取.env文件中的配置信息,dotenv通过读取.env文件内容,并在解析后将键值对添加到process.env对象中

npm i dotenv

创建配置文件.env

// .env文件
APP_PORT = 8000

创建配置文件src/config/config.default.js

const dotenv = require('dotenv');
dotenv.config();
​
module.exports = process.env

改写main.js文件

​
const { APP_PORT } = require('./config/config.default')
const Koa = require('koa')
const app = new Koa()
​
// 中间件
app.use((ctx,next)=>{
    ctx.body = 'hello Koa'
})
​
//  监听端口
app.listen(APP_PORT,()=>{
    console.log('server is running at http://127.0.0.1:' + APP_PORT)
})

四、添加路由

路由---根据不同的URL,调用对应的处理函数

1、安装路由

npm i koa-router

使用步骤:1、导入包;2、实例化对象;3、编写路由;4、注册中间件

改写main.js

const Koa = require('koa')
const Router = require('koa-router')
const { APP_PORT } = require('./config/config.default')
const app = new Koa()
const router = new Router()
​
// 路由
router.get('/', async(ctx,next)=>{
    ctx.body = 'hello Koa'
})
// 中间件
app.use(router.routes())
​
//  监听端口
app.listen(APP_PORT,()=>{
    console.log('server is running at http://127.0.0.1:' + APP_PORT)
})
2、编写路由

创建src/router/user.route.js

const Router = require('koa-router')
const userRouter = new Router({prefix:'/users'})
​
userRouter.get('/', async(ctx,next)=>{
    ctx.body = 'hello users'
})
​
module.exports = userRouter

改写main.js

const Koa = require('koa')
const { APP_PORT } = require('./config/config.default')
const userRouter = require('./router/user.route')
const app = new Koa()
​
// 中间件
app.use(userRouter.routes())
​
//  监听端口
app.listen(APP_PORT,()=>{
    console.log('server is running at http://127.0.0.1:' + APP_PORT)
})

五、目录结构优化

1、将http服务和app业务拆分

创建src/app/index.js

const Koa = require('koa')
const userRouter = require('./router/user.route')
const app = new Koa()
​
// 中间件
app.use(userRouter.routes())
​
modelu.exports = app
2、将路由和控制器拆分

路由---解析URL,分发给控制器对应的方法

控制器---处理不同的业务

创建src/controller/user.controller.js

class UserController {
    async register(ctx,next) {
        ctx.body = "用户注册成功"
    }
    
    async login(ctx,next) {
        ctx.body = "用户登录成功"
    }
}

改写src/router/user.route.js

const Router = require('koa-router')
const router = new Router({prefix:'/users'})
const {register, login} = require('../controller/user.controller')
​
// 注册接口
router.post('/register', register)
​
// 登录接口
router.post('/login', login)
// 导出路由
​
module.exports = router

六、解析body

1、安装koa-body

npm i koa-body

2、注册中间件

改写app/index.js

const Koa = require('koa')
const { koaBody } = require('koa-body')
const userRouter = require('../router/user.route')
​
const app = new Koa()
​
app.use(koaBody())
//  路由中间件
app.use(userRouter.routes())
module.exports = app;
3、解析请求数据

改写user.controller.js

/**
 *  用户控制模块
 */
const { createUser } = require('../service/user.service')
​
class UserController {
​
    async register(ctx, next) {
        // 1 获取数据
        const { username, password } = ctx.request.body;
        // 2 操作数据库
        const res = await createUser(username, password);
        // 3 返回数据
        ctx.body = res
    }
​
    async login(ctx, next) {
        ctx.body = "用户登录成功"
    }
}
​
module.exports = new UserController()
​
4、拆分service层

service层主要用于数据库处理

创建src/service/user.service.js

class UserService {
    async createUser(username, password) {
         return "写入数据库成功"
    }
}
​
module.exports = new UserService();

七、数据库操作

1、sequlize ORM 数据库工具

ORM:对象关系映射

1、数据表映射对应一个类

2、数据表中的数据行(记录)对应一个对象

3、数据表中的字段对应对象的属性

4、数据表的操作对应对象的方法

2、安装sequlize和mysql2

npm i --save sequlize mysql2

3、连接数据库

修改.env文件

# .env文件
APP_PORT = 8000
​
# 配置数据库
DB_HOST = localhost
DB_PORT = 3306
DB_USER = root
DB_PASSWORD = wlg1213
DB_NAME = data_analysis

创建src/db/seq.js文件

const { Sequelize } = require('sequelize')
const { 
    DB_HOST, 
    DB_USER, 
    DB_PORT, 
    DB_PASSWORD,
    DB_NAME 
} = require('../config/config.default')
const seq = new Sequelize(DB_NAME, DB_USER, DB_PASSWORD, {
    host: DB_HOST,
    dialect: 'mysql',
    logging: false,
    timezone: '+08:00'    // 保存为本地时区
})
​
// seq.authenticate().then(() => {
//     console.log('数据库连接成功')
// }).catch(err => {
//     console.log('数据库连接失败', err)
// })
​
module.exports = seq

八、创建User模型

sequelize主要通过model操作对应数据表

创建src/model/user.model.js

const { DataTypes } = require('sequelize');
const seq  = require('../db/seq')
​
// 定义模型
const User = seq.define('User', {
    // 在这里定义模型属性
    username: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true,
        comment: '用户名, 唯一'
    },
    password: {
        type: DataTypes.CHAR(64),
        allowNull: false,
        comment: '密码'
    },
    is_admin: {
        type: DataTypes.BOOLEAN,
        allowNull: false,
        defaultValue: 0,
        comment: '是否管理员 0:否 1:是'
    }
}, {
    // 这是其他模型参数
    // timestamps: false
});
// User.sync({ force: true });   // 自动创建表
module.exports = User;

九、添加用户入库

改写src/service/user.service.js

const User = require('../model/user.model')
​
class UserService {
    async createUser(username, password) {
        // todo: 写入数据库
        // 插入数据
        // await表达式:Promise对象的值
        const res = await User.create({username, password})
        // console.log(res)
        return res.dataValues
    }
}
​
module.exports = new UserService()

改写src/constroller/user.controller.js

const { createUser } = require('../service/user.service')
​
class UserController {
    async register(ctx, next) {
        // 1.获取数据
        console.log(ctx.request.body)
        const { username, password} = ctx.request.body
        // 2.操作数据库
        const res = await createUser(username, password)
        // 3.返回结果
        ctx.body = {
            code: 0,
            message: '用户注册成功',
            result: {
                id: res.id,
                username: res.username
            }
        }
    }
​
    async login(ctx, next) {
        ctx.body = '登录成功'
    }
}
​
module.exports = new UserController()

十、错误处理

1、合法性处理

改写src/controller/user.controller.js

/**
 *  用户控制模块
 */
const { createUser, getUserInfo } = require('../service/user.service')
​
class UserController {
​
    async register(ctx, next) {
        // 1 获取数据
        const { username, password } = ctx.request.body;
        // 合法性验证
        if (!username || !password) {
            console.error('用户名或密码为空',ctx.request.body)
            ctx.status = 400;
            ctx.body = {
                code: '10001',
                message: '用户名或密码为空',
                result: {
​
                }
            }
            return;
        }
        // 合理性验证
        if(getUserInfo({username})){
            ctx.status = 409;
            ctx.body = {
                code: '10002',
                message: '用户名已存在',
                result: {
                }
            }
            return
        }
        // 2 操作数据库
        const res = await createUser(username, password);
        // 3 返回数据
        ctx.body = {
            code: 0,
            message: '用户注册成功',
            result: {
                id: res.id,
                username: res.username
            }
        }
    }
​
    async login(ctx, next) {
        ctx.body = "用户登录成功"
    }
}
​
module.exports = new UserController()
​
​
2、合理性处理

改写改写src/service/user.service.js

const User = require("../model/user.model.js");
​
class UserService {
    // 创建用户
    async createUser(username, password) {
        // 利用用户模型插入数据
        // await 表达式: promise 对象的值
        const res = await User.create({
            username,
            password
        })
        // console.log(res);
        return res.dataValues;
    }
​
    // 查询用户是否存在
    async getUserInfo({id, username, password,is_admin}) {
        const whereOpt = {}
        id && Object.assign(whereOpt, {id})
        username && Object.assign(whereOpt, {username})
        password && Object.assign(whereOpt, {password})
        is_admin && Object.assign(whereOpt, {is_admin})
        
        // 查询数据
        const res = await User.findOne({
            // 查询的字段
            attributes: ['id', 'username', 'password','is_admin'],
            where: whereOpt,
        })
        return res?res.dataValues:null;
    }
}
​
module.exports = new UserService();

十一、拆分中间件

利用中间件处理一些校验的问题

创建src/middleware/user.middleware.js

const { getUserInfo } = require('../service/user.service')
//  验证用户合法性
const userValidator = async (ctx, next) => {
    const { username, password } = ctx.request.body;
    if (!username || !password) {
        console.error('用户名或密码为空',ctx.request.body)
        ctx.status = 400;
        ctx.body = {
            code: '10001',
            message: '用户名或密码为空',
            result: {
​
            }
        }
        return;
    }
    await next();
}
​
//  验证用户名的合理性
const verifyUser = async (ctx, next) => {
    const { username } = ctx.request.body;
    if(getUserInfo({username})){
        ctx.status = 409;
        ctx.body = {
            code: '10002',
            message: '用户名已存在',
            result: {
            }
        }
        return
    }
    await next();
}
​
module.exports = {
    userValidator,
    verifyUser
}
​

在router中的应用

改写src/router/user.router.js

const Router = require('koa-router')
const router = new Router({prefix:'/users'})
const { userValidator,verifyUser } = require('../middleware/user.middleware')
const {register, login} = require('../controller/user.controller')
​
​
// 注册接口
router.post('/register',userValidator,verifyUser, register)
​
// 登录接口
router.post('/login', login)
// 导出路由
​
module.exports = router

十二、统一的错误处理

1、建立错误码

创建src/constant/err.type.js

module.exports = {
    userFormateError:{
        code:'10001',
        message:'用户名或密码为空',
        result:''
    },
    userAlreadyExited:{
        code:'10002',
        message:'用户已经存在',
        result:''
    },
    userRegisterError:{
        code:'10003',
        message:'用户注册失败',
        result:''
    }
}

在处理校验的中间件中引用,并提交错误

改写src/middleware/user.middleware.js

​
const { getUserInfo } = require('../service/user.service')
const {userFormateError,userAlreadyExited} = require('../constant/err.type')
//  验证用户合法性
const userValidator = async (ctx, next) => {
    const { username, password } = ctx.request.body;
    if (!username || !password) {
        console.error('用户名或密码为空',ctx.request.body)
        // ctx.status = 400;
        ctx.app.emit('error', userFormateError, ctx);
        return;
    }
    await next();
}
​
//  验证用户名的合理性
const verifyUser = async (ctx, next) => {
    const { username } = ctx.request.body;
    if(getUserInfo({username})){
        console.error('用户名已存在',ctx.request.body)
        ctx.app.emit('error', userAlreadyExited, ctx);
        return
    }
    await next();
}
​
module.exports = {
    userValidator,
    verifyUser
}
​
2、处理错误状态

对于提交的错误信息,在src/app/index.js中进行统一处理

const Koa = require('koa')
const { koaBody } = require('koa-body')
const errHandler = require('./errHandler')
const userRouter = require('../router/user.route')
​
const app = new Koa()
​
app.use(koaBody())
app.use(userRouter.routes())
​
// 统一的错误处理
app.on('error', errHandler)
module.exports = app;

创建src/app/errHandler.js

module.exports = (error, ctx) => {
    let status = 500
    switch (error.code) {
        case '10001':
            status = 400
        break
        case '10002':
            status = 409
        break
        default:
            status = 500
    }
    ctx.status = status
    ctx.body = error
​
}

十三、错误处理的补充与完善

对于src/middleware/user.middleware.js中验证用户合理性进行完善

对于调用service层,应该用try...catch进行异常捕获

const { getUserInfo } = require('../service/user.service')
const {userFormateError,userAlreadyExited,userRegisterError} = require('../constant/err.type')
//  验证用户合法性
const userValidator = async (ctx, next) => {
    const { username, password } = ctx.request.body;
    if (!username || !password) {
        console.error('用户名或密码为空',ctx.request.body)
        // ctx.status = 400;
        ctx.app.emit('error', userFormateError, ctx);
        return;
    }
    await next();
}
​
//  验证用户名的合理性
const verifyUser = async (ctx, next) => {
    const { username } = ctx.request.body;
    try {
        const res = await getUserInfo({ username })
        if(res){
            console.error('用户名已存在',ctx.request.body)
            ctx.app.emit('error', userAlreadyExited, ctx);
            return
        }
    } catch (error) {
        console.error('获取用户信息错误',error)
        ctx.app.emit('error', userRegisterError, ctx)
        return
    }
    await next();
}
​
module.exports = {
    userValidator,
    verifyUser
}
​

对于src/controller/user.controller.js,调用service层应该也用try...catch

异常捕获

/**
 *  用户控制模块
 */
const { createUser } = require('../service/user.service')
const { userRegisterError} = require('../constant/err.type')
​
class UserController {
​
    async register(ctx, next) {
        // 1 获取数据
        const { username, password } = ctx.request.body;
        // 2 操作数据库
        try {
            const res = await createUser(username, password);
            // 3 返回数据
            ctx.body = {
                code: 0,
                message: '用户注册成功',
                result: {
                    id: res.id,
                    username: res.username
                }
            }
        } catch (error) {
            console.error('用户注册失败',error)
            ctx.app.emit('error', userRegisterError, ctx)
        }    
    }
​
    async login(ctx, next) {
        ctx.body = "用户登录成功"
    }
}
​
module.exports = new UserController()
​

十四、加密

在将密码保存到数据库之前,要对密码进行加密处理

bcrypt.js 零依赖,加盐加密

1、安装bcryptjs
npm i bcryptjs
2、加密处理

加密做成一个中间件

改写src/middleware/user.middleware.js

const bcrypt = require('bcryptjs')
const { getUserInfo } = require('../service/user.service')
const {userFormateError,userAlreadyExited,userRegisterError} = require('../constant/err.type')
​
//  密码处理
const crpytPassword = async (ctx, next) => {
    const { password } = ctx.request.body;
    const salt = bcrypt.genSaltSync(10);
    // hash保存的是密文
    const hash = bcrypt.hashSync(password, salt);
    ctx.request.body.password = hash;
    await next();
}
​
module.exports = {
    crpytPassword
}

十五、用户登录

1、用户验证

改写登录接口

// 登录接口
router.post('/login',userValidator,verifyLogin, login)

验证用户

const verifyLogin = async (ctx, next) => {
    const { username, password } = ctx.request.body;
    try {
        const res = await getUserInfo({ username })
        // 验证用户是否存在
        if (!res) {
            console.error('用户不存在',{username})
            ctx.app.emit('error', userDoesNotExist, ctx);
            return;
        }
        // 验证密码
        const isMatch = bcrypt.compareSync(password, res.password);
        if (!isMatch) {
            console.error('密码无效',ctx.request.body)
            ctx.app.emit('error', invalidePassword, ctx);
            return;
        }
    } catch (error) {
        console.error('用户登录失败',error)
        ctx.app.emit('error', userLoginError, ctx)
        return
    }
    await next()
}

更新错误码类型

module.exports = {
    userDoesNotExist: {
        code:'10004',
        message:'用户不存在',
        result:''
    },
    userLoginError: {
        code:'10005',
        message:'用户登录失败',
        result:''
    },
    invalidePassword: {
        code:'10006',
        message:'密码不匹配',
        result:''
    }
}
2、用户认证

登录成功后,给用户颁发一个令牌token,用户在以后得每一次请求中携带这个令牌

JWT:JSON WEB TOKEN

JWT分为三部分:header头部、playload载荷、signature签名

安装jsonwebtoken

npm i jsonwebtoken
async login(ctx, next) {
        const { username } = ctx.request.body;
        // 1 获取用户信息 (在token中要包含用户的信息,id,username,is_admin)
        try {
            const res = await getUserInfo({ username })
                // 从返回结果对象中除去password属性,将剩下的属性放到res对象中
                const { password, ...resUser } = res
                ctx.body = {
                    code: 0,
                    message: '用户登录成功',
                    result: {
                        id: resUser.id,
                        username: resUser.username,
                        is_admin: resUser.is_admin,
                        token: jwt.sign(resUser,JWT_SECRET, { expiresIn: '1d' })
                    }
                }
        } catch (error) {
            console.error('用户登录失败', error)
            ctx.app.emit('error', userLoginError, ctx)
        }
    }

十六、修改密码

1、鉴权中间件

创建src/middleware/auth.middleware.js

const jwt = require('jsonwebtoken')
const { JWT_SECRET } = require('../config/config.default')
const { tokenExpiredError,invalideToken } = require('../constant/err.type')
const auth = async (ctx, next) =>{
    const { authorization } = ctx.request.headers
    const token = authorization.replace('Bearer ','')
    try {
        // user中包含了payload的信息(id,username,is_admin)
        const user = jwt.verify(token, JWT_SECRET)
        ctx.state.user = user
    } catch (error) {
        switch (error.name) {
            case 'TokenExpiredError':
                console.error('token已过期',error)
                return ctx.app.emit('error', tokenExpiredError, ctx)
            case 'JsonWebTokenError':
                console.error('无效的token',error)
                return ctx.app.emit('error', invalideToken, ctx)
            default:
                break
        }
    }
    await next()
}
​
module.exports = {
    auth
}
2、修改密码路由
// 修改密码
router.patch('/',auth,crpytPassword,changePassword )
3、修改密码控制器
 // 修改密码
    async changePassword(ctx, next) {
        // 1 获取数据
        const { id } = ctx.state.user;
        const { password } = ctx.request.body;
        console.log(id, password);
        // 2 操作数据库
        try {
            const res = await updateById({ id, password });
            console.log(res);
            if (res) {
                ctx.body = {
                    code: 0,
                    message: '修改密码成功',
                    result: ''
                }
            } else {
                ctx.body = {
                    code: 1,
                    message: '修改密码失败',
                    result: ''
                }
            }
        } catch (error) {
            console.error('修改密码异常', error)
            ctx.app.emit('error', updatePasswordErr, ctx)
        }
    }
4、操作service层updateById
 // 修改用户信息
    async updateById({id, username, password, is_admin}) {
        const whereOpt = {id};
        const newUser = {};
    
        username && Object.assign(newUser, {username});
        password && Object.assign(newUser, {password});
        is_admin && Object.assign(newUser, {is_admin});
        console.log(newUser);
        
        const res = await User.update(newUser, {
            where: whereOpt,
        });
        return res[0] > 0 ? true : false;
    }

十七、路由自动加载

由于每一个模块都要写一个路由,在app/index.js里面注册,这样代码就比较臃肿,利用fs模块,让路由自动加载

创建src/router/index.js;获取该目录下除了index.js

const fs = require('fs')
const Router = require('koa-router')
const router = new Router()
​
fs.readdirSync(__dirname).forEach(file=>{
    // console.log(file);
    if(file !== 'index.js'){
        const r = require(`./${file}`)
        router.use(r.routes())
    }
})
​
module.exports = router;

app/index.js中注册路由

​
const Koa = require('koa')
const { koaBody } = require('koa-body')
const errHandler = require('./errHandler')
const router = require('../router')
// console.log(router);
​
const app = new Koa()
​
app.use(koaBody())
app.use(router.routes())
// 统一的错误处理
app.on('error', errHandler)
module.exports = app;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值