零、项目结构

main.js是项目的入口文件,导入了config/config.default.js文件和app/index.js文件。

config/config.default.js文件是读取你的.env文件,将配置写到process.env

.env 全局默认配置文件

app/index.js是项目的app服务,导入了koa框架,koa-body并使用,app/errHandler 错误处理中间件和router/user.router路由模块

router/user.router是项目的路由文件,导入使用了koa-router,middleware/user.middlewarecontroller/user.controller

controller/user.controller是项目的控制器,用于接口的编写,导入了一个service/user.serviceconsitant/err.type

service/user.service主要是做数据库处理,导入了model/user.model,调用model完成数据库操作

model/user.model 编写数据表,导入了db/seq连接数据库,用于service/user.service数据库处理

db/seq连接数据库,导入了.env 全局默认配置文件

middleware/user.middleware为中间件文件,用于处理服务请求的细节,导入了service/user.serviceconstant/err.type

constant/err.type为统一错误定义的文件,定义一些请求中的错误原因

app/errHandler为错误处理函数,需要在app服务中on()绑定错误

一、项目初始化

1.npm初始化

1
npm init -y

生成package.json文件

  • 记录项目的依赖

2.git初始化

1
git init

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

二、搭建项目

1.安装Koa框架

1
npm i koa

2.编写最基本的app

新建一个/src/main.js文件

1
2
3
4
5
6
7
8
9
10
const Koa = require('koa')

const app = new Koa()

app.use((ctx,next)=>{
ctx.body = "hello api"
})
app.listen(3000,()=>{
console.log("server is running on http://localhost:3000");
})

3.测试

在终端,使用命令

1
node src/main.js

或者有安装 nodemon

1
nodemon src/main.js

浏览器显示 hello api

三、项目的基本优化

1.自动重启服务

全局安装nodemon工具

1
npm i nodemon -g

编写package.json脚本

1
2
3
4
"scripts": {
"dev": "nodemon ./src/main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},

执行npm run dev启动服务

2.读取配置文件

安装dotenv, 读取根目录中的.env文件, 将配置写到process.env

1
npm i dotenv

在项目根目录中创建.env文件

1
APP_PORT=8000

创建src/config/config.default.js

1
2
3
4
5
6
7
const dotenv = require('dotenv')

dotenv.config()
// console.log(process.env.APP_PORT );

//process 模块是 nodejs 提供给开发者用来和当前进程交互的工具
module.exports = process.env

修改main.js

1
2
3
4
5
6
7
8
9
10
11
12
const Koa = require('koa')

const {APP_PORT} = require('./config/config.default')
const app = new Koa()

app.use((ctx,next)=>{
ctx.body = "hello api"

})
app.listen(APP_PORT,()=>{
console.log(`server is running on http://localhost:${APP_PORT}`);
})

四、添加路由

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

1.安装koa-router

1
npm i koa-router

步骤:

  1. 导入包
  2. 实例化对象
  3. 编写路由
  4. 注册中间件

2.编写路由

创建src/router目录, 编写user.router.js

1
2
3
4
5
6
7
8
9
10
//导入路由
const Router = require('koa-router')
//实例化
const router = new Router({ prefix: '/users'})
//编写路由接口
router.get('/',(ctx,next)=>{
ctx.body = 'hello users'
})
//暴露路由对象
module.exports = router

3.修改main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const Koa = require('koa')

const {APP_PORT} = require('./config/config.default')
//导入/router/user.router文件
const userRouter = require('./router/user.router')

const app = new Koa()
//使用
app.use(userRouter.routes())

app.listen(APP_PORT,()=>{
console.log(`server is running on http://localhost:${APP_PORT}`);
})

五、目录结构优化

1.将 http 服务和 app 服务拆分

创建src/app/index.js文件

1
2
3
4
5
6
7
8
9
const Koa = require('koa')

const userRouter = require('../router/user.router')

const app = new Koa()

app.use(userRouter.routes())

module.exports = app

修改main.js

1
2
3
4
5
6
7
const {APP_PORT} = require('./config/config.default')

const app = require("./app")

app.listen(APP_PORT,()=>{
console.log(`server is running on http://localhost:${APP_PORT}`);
})

2.将路由和控制器拆分

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

控制器: 处理不同的业务

修改user.route.js

1
2
3
4
5
6
7
8
9
10
11
12
const Router = require('koa-router')

const {register,login} = require('../controller/user.controller')

const router = new Router({ prefix: '/users'})

//注册接口
router.post('/register',register)
//登录接口
router.post('/login',login)

module.exports = router

创建controller/user.controller.js

1
2
3
4
5
6
7
8
9
10
11
class UserController {
async register(ctx, next) {
ctx.body = '用户注册成功'
}

async login(ctx, next) {
ctx.body = '登录成功'
}
}

module.exports = new UserController()

六. 解析 body

1.安装 koa-body

1
npm i koa-body

2.注册中间件

改写app/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const Koa = require('koa')
//导入koa-body
const KoaBody = require('koa-body')
const userRouter = require('../router/user.router')

const app = new Koa()

//注册koabody中间件的时候要在所有路由之前
app.use(KoaBody())

app.use(userRouter.routes())

module.exports = app

3.解析请求数据

修改user.controller.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const { createUser } = require('../service/user.service')

class UserController {
async register(ctx, next) {
// 获取数据
// console.log(ctx.request.body);
const { user_name, password } = ctx.request.body
// 操作数据库
const res = await createUser(user_name, password)
console.log(res);
// 返回结果
ctx.body = ctx.request.body
}

async login(ctx, next) {
ctx.body = '登录成功'
}
}

module.exports = new UserController()

4.拆分 service 层

service 层主要是做数据库处理

创建src/service/user.service.js

1
2
3
4
5
6
7
8
class UserService {
async createUser(user_name,password){
//todo:写入数据库
return '写入数据库成功'
}
}

module.exports = new UserService()

七. 数据库操作

sequelize ORM 数据库工具

ORM: 对象关系映射

  • 数据表映射(对应)一个类
  • 数据表中的数据行(记录)对应一个对象
  • 数据表字段对应对象的属性
  • 数据表的操作对应对象的方法

1.安装 Sequelize 和 MySQL

1
npm i mysql2 sequelize

2.连接数据库

新建src/db/seq.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { Sequelize } = require('sequelize')
const { MYSQL_HOST,
MYSQL_PORT,
MYSQL_USER,
MYSQL_PWD,
MYSQL_DB } = require('../config/config.default')

const seq = new Sequelize(MYSQL_DB, MYSQL_USER, MYSQL_PWD, {
host: MYSQL_HOST,
dialect: 'mysql'
})

seq.authenticate().then(() => {
console.log('数据库连接成功');
}).catch((err) => {
console.log('数据库连接失败', err);
})

3.编写配置文件

修改.env

1
2
3
4
5
6
7
APP_PORT = 8000

MYSQL_HOST = localhost
MYSQL_PORT = 3306
MYSQL_USER = root
MYSQL_PWD = 123456
MYSQL_DB = zdsc

八. 创建 User 模型

拆分 Model 层

sequelize 主要通过 Model 对应数据表

创建src/model/user.model.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const {DataTypes} = require('sequelize')
const seq = require('../db/seq')

//创建模型
const User = seq.define('zd_user',{
//id会被sequelize自动创建
user_name:{
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

九. 添加用户入库

所有数据库的操作都在 Service 层完成, Service 调用 Model 完成数据库操作

修改src/service/user.service.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const User = require('../model/user.model')
class UserService {
async createUser(user_name, password) {
//todo:写入数据库

//await表达式:promise对象的值
const res = await User.create({
user_name, password
})
//console.log(res);
return res.dataValues
}
}

module.exports = new UserService()

修改user.controller.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const { createUser } = require('../service/user.service')

class UserController {
async register(ctx, next) {
// 获取数据
console.log(ctx.request.body);
const { user_name, password } = ctx.request.body
// 操作数据库
const res = await createUser(user_name, password)
console.log(res);
// 返回结果
ctx.body = {
code:200,
message:'用户注册成功',
result:{
id:res.id,
user_name:res.user_name
}
}
}

async login(ctx, next) {
ctx.body = '登录成功'
}
}

module.exports = new UserController()

十. 错误处理

在控制器中, 对不同的错误进行处理, 返回不同的提示错误提示, 提高代码质量

user.controller.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const { createUser,getUserInfo } = require('../service/user.service')

class UserController {
async register(ctx, next) {
// 获取数据
console.log(ctx.request.body);
const { user_name, password } = ctx.request.body
//合法性
if(!user_name || !password){
console.error('用户名或密码为空',ctx.request.body);
ctx.status = 400,
ctx.body = {
code: '400',
message: '用户名或密码为空',
result: ''
}
return
}
//合理性
if(getUserInfo({user_name})){
ctx.status = 409,
ctx.body = {
code:'409',
message: '用户已经存在',
result:''
}
return
}
// 操作数据库
const res = await createUser(user_name, password)
// console.log(res);
// 返回结果
ctx.body = {
code:200,
message:'用户注册成功',
result:{
id:res.id,
user_name:res.user_name
}
}
}

async login(ctx, next) {
ctx.body = '登录成功'
}
}

module.exports = new UserController()

在 service 中封装函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const User = require('../model/user.model')
class UserService {
async createUser(user_name, password) {
//todo:写入数据库

//await表达式:promise对象的值
const res = await User.create({
user_name, password
})
//console.log(res);
return res.dataValues
}

async getUserInfo({id,user_name,password,is_admin}) {
const whereOpt = {}
id && Object.assign(whereOpt,{id})
user_name && Object.assign(whereOpt,{user_name})
password && Object.assign(whereOpt,{password})
is_admin && Object.assign(whereOpt,{is_admin})

const res = await User.findOne({
attributes:['id','user_name','password','is_admin'],
where:whereOpt
})
return res ? res.dataValues : null
}

}

module.exports = new UserService()

十一、拆分中间件

  • 在出错的地方使用ctx.app.emit提交错误
  • 在 app 中通过app.on监听

1.拆分中间件

添加src/middleware/user.middleware.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const { getUserInfo } = require('../service/user.service')
const { userFormateError, userAlreadyExited,userRegisterError} = require('../consitant/err.type')
const userValidator = async (ctx, next) => {
const { user_name, password } = ctx.request.body
//合法性
if (!user_name || !password) {
console.error('用户名或密码为空', ctx.request.body);
ctx.app.emit('error', userFormateError, ctx)
return
}
await next()
}
const userVerify = async (ctx, next) => {
const { user_name } = ctx.request.body
//合理性
// if (await getUserInfo({ user_name })) {
// ctx.app.emit('error',userAlreadyExited,ctx)
// return
// }
try {
const res = await getUserInfo({user_name})
if (res) {
console.error('用户名已经存在',{user_name});
ctx.app.emit('error',userAlreadyExited,ctx)
return
}
} catch (err) {
console.error('获取用户信息错误',err);
ctx.app.emit('error',userRegisterError,ctx)
return
}
await next()
}
module.exports = { userValidator, userVerify }

2.统一错误处理

编写统一的错误定义文件

新建src/consitant/err.type.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
userFormateError:{
code: '400',
message: '用户名或密码为空',
result:''
},
userAlreadyExited:{
code: '409',
message: '用户已存在',
result:''
},
userRegisterError:{
code: '400',
message: '用户注册失败',
result:''
}
}

3.错误处理函数

新建src/app/errHandler.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = (err,ctx) => {
let status = 500
switch (err.code) {
case '400':
status = 400
break;
case '409':
status = 409
break;
default:
status = 500
}
ctx.status = status
ctx.body = err
}

修改app/index.js

1
2
3
const errHandler = require('./errHandler')
// 统一的错误处理
app.on('error', errHandler)