Skip to main content

NodeJS - Koa开发服务端

作用: 

1. 脱离浏览器运行JS


2. NodeJS Stream (前端工程化基础)


3. 服务端API


4. 作为中间层



使用NodeJS开发服务端 (使用koa)

- npm init

- npm install koa


在根目录下面创建app.js,并且引入Koa

然后实例化Koa。可以称app为应用程序对象,里面包含很多中间件

const Koa = require("koa")
const app = new Koa()
app.listen(3000)

如果要在访问3000端口的时候执行某个函数,可以加入下面的代码来加上中间件

app.use((ctx, next) => {
console.log("hi yingxu1");
})

其中ctx为上下文,next为下一个中间件

当同时挂上多个中间件的时候,只会运行第一个。但是使用next()便可以执行下面一个中间件。

next会返回Promise

app.use((ctx, next) => {
console.log("hi yingxu")
next()
})

app.use((ctx, next) => {
console.log("hi yingxu1");
})


洋葱模型

下面的代码会输出1342

app.use((ctx, next) => {
console.log("1")
next()
console.log("2")
})

app.use((ctx, next) => {
console.log("3")
next()
console.log("4")
})


但是最好在写中间件的时候使用 async/await。这样能保证中间件按顺序执行

app.use(async (ctx, next) => {
console.log("1")
await next()
console.log("2")
})

app.use(async (ctx, next) => {
console.log("3")
await next()
console.log("4")
})

async保证返回的结果一定是Promise。

Koa里面就算不用async关键字,最后也会返回Promise


中间件的传参

可以在一个中间件里面使用ctx.xx = xx

在另外一个中间件里面就可以使用ctx.xx来获取


获取请求的路径

根据官方文档: 

app.use(async ctx => {
  ctx; // is the Context
  ctx.request; // is a Koa Request
  ctx.response; // is a Koa Response
});

ctx.request是请求

ctx.response是返回值


也可以使用别名:

The following accessors and alias Request equivalents:

  • ctx.header
  • ctx.headers
  • ctx.method
  • ctx.method=
  • ctx.url
  • ctx.url=
  • ctx.originalUrl
  • ctx.origin
  • ctx.href
  • ctx.path

等等

所以可以使用以下的语句来获取url路径和方法

app.use(async (ctx, next) => {
console.log(ctx.path)
console.log(ctx.method);
})


Koa返回内容

使用ctx.body

app.use(async (ctx, next) => {
if (ctx.method === "GET" && ctx.path === "/api/user") {
ctx.body = "heihei"
}
})

这里会返回一个字符串。但是如果要返回json类型的话,可以直接将对象赋值给ctx.body。

app.use(async (ctx, next) => {
if (ctx.method === "GET" && ctx.path === "/api/user") {
ctx.body = { key: "halo"}
}
})

Koa内部会将其转为json如下:

{"key": "halo"}


但是如果每个url都这么判断岂不是太麻烦了?

可以使用koa-router


Koa-router

在上面代码的基础上,导入koa-router并且实例化

const Router = require("koa-router");
const router = new Router()
router.get('/api/user', (ctx, next) => {
// ctx.body = { key: "hi" }
console.log("hii");
})

app.use(router.routes())


但是如果都写在app.js里面,可能会略显臃肿。考虑将所有router拆分到不同文件里面,并在app.js里面引用注册

const Koa = require("koa")
const user = require("./api/user")
const stat = require("./api/stat")
const answer = require("./api/answer")

const app = new Koa()

app.use(user.routes())
app.use(stat.routes())
app.use(answer.routes())

app.listen(3000)


但是一个一个注册也是很麻烦,所以考虑使用require-directory的库把所有的router都注册上

const Koa = require("koa")
const Router = require("koa-router")
const requireDirectory = require("require-directory")

const app = new Koa()

requireDirectory(module, "./api", {visit: whenLoadModule})

function whenLoadModule(obj) {
if (obj instanceof Router) {
app.use(obj.routes())
}
}

app.listen(3000)


接收传参

const path = ctx.request.param // get :id
const query = ctx.request.query // get ?xxx
const headers = ctx.request.header // get headers
const body = ctx.request.body // get body content

要获取body的内容必须使用一个库: koa-bodyparser

并且在app上面挂载

const parser = require("koa-bodyparser")

const app = new Koa()
app.use(parser())


跨域

在koa中解决跨域很简单:

安装@koa/cors

并且挂载到app上就行

const cors = require("@koa/cors")
app.use(cors())


可以全局统一处理异常问题,定一个catchError中间件并挂载到app上

const catchError = async (ctx, next) => {
try {
await next()
} catch (error) {
// 开发环境 | 生产环境
// if (global.config.environment === "dev") {
// throw error
// }

if (error instanceof HttpException) {
ctx.body = {
msg: error.msg,
errno: error.errno,
request: `${ctx.method} ${ctx.path}`
}
ctx.status = error.status
} else {
ctx.body = {
errno: error.errno,
msg: "Unknown error",
request: `${ctx.method} ${ctx.path}`
}
}
}
}

其中 可以定义 各种xxException extends Error 来对error进行分类

class HttpException extends Error {
constructor(msg = "服务器异常", status = 400) {
super()
this.errno = 1
this.status = status
this.msg = msg
}
}

class ParameterException extends HttpException {
constructor(msg) {
super()
this.errno = 1
this.msg = msg || "参数错误"
this.status = 400;
}
}


Sequelize

需要npm install Sequelize & npm install mysql2

使用sequelize 连接数据库 并且配置数据库的参数

const Sequelize = require("sequelize")
const { dbName, host, port, user, password } = require("../config/config").database

const sequelize = new Sequelize(dbName, user, password, {
dialect: "mysql",
host,
port,
logging: true,
timezone: "+08:00", // 北京时间
define: {
timestamps: true, // createdAt | updatedAt
paranoid: true // deletedAt
}
})

sequelize.sync()

module.exports = {
sequelize
}

 

创建一个User的table

const { sequelize } = require("../core/db")

const { Sequelize, Model } = require("sequelize")

class User extends Model {

}

User.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: Sequelize.STRING,
unique: true
},
nickname: Sequelize.STRING,
password: Sequelize.STRING,
}, {
sequelize,
tableName: 'user' // 数据表的名字
})


创建一个新用户可以先将model导入进来,然后使用User.create(user)即可创建一个新的用户

比如:

// get body content
const user = {
username: body.username,
password: body.password,
nickname: body.nickname
}

// add to database
User.create(user)


在sequelize里面查找一个数据可以用xxx.findOne({where: {username: username}})的形式


密码加密

在数据库中,密码最好不要用明文的方式存储,使用盐加密比较好

使用库: bcryptjs

npm install bcryptjs


使用:

const salt = bcrypt.genSaltSync(10)
const passwordEncrypted = bcrypt.hashSync(body.password, salt)


可以直接在model里面给password传入set函数,直接加密

password: {
type: Sequelize.STRING,
set(val) {
// 对密码进行加密
// 10 => 成本,安全性
const salt = bcrypt.genSaltSync(10)
const passwordEncrypted = bcrypt.hashSync(val, salt)
this.setDataValue("password", passwordEncrypted)
}
}


bcryptjs


令牌


Sequelize

paranoid: true属性可以使得删除一个数据不会直接在数据库里面删除,而是更新在deletedAt这个属性上 软删除。返回的时候不会返回这些已经被删除的元素


事物 in Sequelize

所有操作要么都完成,要么都不完成


Scope in Sequelize

可以标记返回的时候,哪些属性需要,哪些属性不要


分页 并且返回指定页面和搜索内容

const { rows, count } = await Question.findAndCountAll({
where: {
title: {
[Op.like]: `%${keyword}%`
},
user_id: uid
},
order: [
['createdAt', 'DESC']
],
offset: (pageNumber - 1) * pageSizeNumber,
limit: pageSizeNumber
})

其中,rows为所有的数据列表,count为总数, offset为当前页数, limit为一页数量




Comments

Popular posts from this blog

Valgrind

  Using Valgrind to check memory How to use Valgrind -To valgrind the program, run the valgrind command and give it our program name as an argument. -For example, if we want to run ./myProgram hello 42, we can simply run Valgrind ./myProgram hello 42.  Uninitialized Values -When we run the program, we may use uninitialized values. It needs to be fixed. Valgrind can tell us about the use of uninitialized values. But it only tell when the control flow of the program depends on the unitialized value. For example, uninitialized value appears in the conditional expression of an if, or a loop, or in the switch statement. -If we want to know where the uninitialized value is from, we can use Valgrind    --track-origins=yes ./myProgram -Using -fsanitize=address can find a lot of problems that Memcheck cannot.  -We can use Valgrind with GDB to debug. We can run: --vgdb=full --vgdb-error=0., then Valgrind will stop on the first error that it encounters and give control to ...

Error Handling and Exceptions

  Error Handling and Exceptions - The worst possible way to deal with any problem is for the program to produce the wrong answer without informing the user - a silent failure.  - When the program deals with an error by aborting, it should do so with the explicit intention of the programmer. (i.e. the programmer should call assert or check a condition then call abort) And the program should give an error message to the user. Simply segmentation fault due to an invalid memory access is never appropriate. - Sometimes we would prefer to handle the error more gracefully. Graceful error handling requires making the user aware of the problem, as well as allowing the program to continue to function normally. - For example, we would present the user with some options to remedy the situation, like entering a different input, select a different file, retry a failed operation, etc.  - As our programming skills develop, we should get into the practice of writing bullet proof code - co...

ADTs

  ADTs 1. Queue -FIFO We can implement in this way:  template<typename T> class Queue { public:     void enqueue(const T & item);     T dequeue();     T & peek()     const T & peek() const;     int numbebrOfItem() const; }; - In the actual C++ STL library, it has the following functions: push() pop() peek() 2. Stack - LIFO We can implement in this way: template<typename T> class Stack { public:     void push(const T & item);     T pop();     T & peek();     const T & peek() const;     int numberOfItem() const; }; In the C++ STL library, it has similar functions as queue. 3. Sets - Add - Test if an item is in the set - Check if the set is empty - Take the union of two set - intersect two sets A variant of this ADT is a multiset. (Also called a bag) It allows the same element to appear multiple times, whereas a true set either has an object or n...