作用:
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
Post a Comment