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

前端 优化代码体积

当我使用npm run build的时候,项目构建了很久。所以考虑用create-react-app网站下面的工具来缩小代码体积  Analyzing the Bundle Size https://create-react-app.dev/docs/analyzing-the-bundle-size 更改完成后 npm run build npm run analyze 可以看到以下的图片: 其中main.js有1.57mb 然后起服务 serve -s build -l 8000 进入到首页之后,打开network,查看js,发现main.js有500kb。这个500kb是已经用gzip压缩过了,但是却还有这么大。500*3=1500说明源文件有1.5mb左右 其中, antd占了25%, recharts占了13%, react-dom占了7.6%,dnd-kit占了2.8% 其中recharts用于统计页面,dnd-kit用于拖拽排序-编辑器页面。 所以在加载首页的时候,先不加载编辑页面和统计页面的js的话,体积会小很多。 路由懒加载 因为项目中,体积占比最大的是Edit和Stat页面(编辑和统计页面),所以考虑使用路由懒加载,拆分bundle,优化首页体积 router文件中,之前: import Edit from "../pages/question/Edit" ; import Stat from "../pages/question/Stat" ; 现在: const Edit = lazy (() => import ( "../pages/question/Edit" )); const Stat = lazy (() => import ( "../pages/question/Stat" )); 为了让生成的文件更加可读,可以改成下面这样: const Edit = lazy ( () => import ( /* webpackChunkName: "editPage" */ "../pages/question/Edit" ) ); const Stat ...

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