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

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...

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 ...