第一章:Node.js与内置模块(fs文件系统、path路径模块、http服务器模块)
第二章:Nodejs模块化(npm与包、开发自己的包、模块加载机制)
第三章:Nodejs之Express(基本使用、Express路由)(一)
第四章:Nodejs之Express( Express 中间件、中间件的分类、自定义中间件)(二)
第五章:Nodejs之解决接口跨域问题
第六章:Nodejs操作Mysql数据库
第七章:Node之前后端身份认证(Session认证机制、JWT 认证机制)
第八章:Nodejs之egg基本使用(初始化项目、内置对象、egg路由、egg控制器)
简单来说,Service 就是在复杂业务场景下⽤于做业务逻辑封装的⼀个抽象层,提供这个抽象有以下⼏个好处:
// app/services/student.js 该示例中的sql相关在egg-mysql章节专⻔有讲解。
const { Service } = require('egg')
class StudentService extends Service {
async findById(id) {
let sql = "select * from tbl_student where id = ?"; //问号代表占位符,之后被query⽅法的第⼆个参数数组中的数据填充
let student = await this.app.mysql.query(sql, [id]);
return student;
}
// student {id,name,gender}
async saveOrUpdate(student) {
let result = null;
if (student.id) {
result = await this.app.mysql.update('tbl_student', student)
} else {
result = await this.app.mysql.insert('tbl_student', student)
}
return result;
}
async deleteById(id) {
let result = await this.app.mysql.delete('tbl_student', { id
})
}
}
module.exports = StudentService;
服务的使⽤: 服务⼀般被controller使⽤。在controller中⽤ctx.services.student.findById(1)来访问service中的⽅法。
如下完整例⼦
app/router.js
module.exports = ( app) =>{
app.router.get('/student/findAll ', controller.student.findAll);
};
app/controller/student.js
const Controller = require('egg').Controller;
class StudentController extends Controller {
async findAll() {
const student = await this.ctx.service.student.findAll();
this.ctx.response.body = student;
}
}
module.exports = StudentController;
app/service/student.js
const Service = require('egg').Service;
class StudentService extends Service {
async findAll() {
let sql = 'select * from tbl_student'
let result = await this.app.mysql.query(sql);
return result;
}
}
module.exports = StudentService;
// curl http://127.0.0.1:7001/student/findAll

egg-mysql: https://www.npmjs.com/package/egg-mysql
$ npm install --save egg-mysql
config/plugin.js
'use strict';
/** @type Egg.EggPlugin */
module.exports = {
mysql: {
enable: true, package: 'egg-mysql',
}
};

config.default.js
const userConfig = {
// myAppName: 'egg',
mysql: {
// 单数据库信息配置
client: {
// host
host: '127.0.0.1',
user: 'zs',
password: '123456',
database: 'table',
port: '3306',
},
// 是否加载到 app 上,默认开启
app: true,
// 是否加载到 agent 上,默认关闭
agent: false,
}
};

service/student.js
const { Service } = require('egg')
class StudentService extends Service {
async findById(id) {
let student = await this.app.mysql.query('select * from tbl_student where id = ? ', [id])
return student;
}
}
module.exports = StudentService;
在egg中可以编写 CRUD 语句来完成增删改查。
CRUD: 增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)
insert(table,{}) 插入
table为表名,{} 为插⼊的字段与值的映射对象
mysql.insert('tbl_student', { name: "terry", gender: "男" })
// 与该条sql语句的结果⼀致:insert into tbl_student (name,gender)
values("terry","男")
get(table,{}) 查询
table为表名,{}为where条件
mysql.get('tbl_student', { id: 1 })
// 与该条sql语句的结果⼀致:select * from tbl_student where id = 1;
select(table,{}) 查询
table为表名,{} 为对象,对象中包含了where、columns、orders、limit、offset等属性
// 查询全表的数据
const results = await this.app.mysql.select('tbl_student');
// 与该条sql语句的结果⼀致:select * from tbl_student;
// 条件查询和结果定制
mysql.select('tbl_student', { //查询表tbl_student
where: { gender: "男" }, // WHERE 条件
columns: ['name', 'gender'], // 要查询的表字段
orders: [['id', 'desc'], ['name', 'desc']], // 排序⽅式
limit: 10, // 返回数据量
offset: 0, // 数据偏移量
})
/* 与该条sql语句的结果⼀致:
select name,gender
from tbl_student
where gender="男"
order by id desc, name desc
limit 0,10;
*/
update(table,{}[,options]) 更新
table为表名,{}为更新后的值,默认根据id来更新
mysql.update('tbl_student', { id: 1, name: "terry", gender: '⼥' })
mysql.update('tbl_student', { name: "terry", gender: '⼥' }, {
where: { id: 1 }
})
/* 与该条sql语句的结果⼀致:
update tbl_student
set name = 'terry',gender = '⼥'
where id = 1;
*/
delete(table,{}) 删除
table为表名,{}为删除条件
mysql.delete(tbl_student, { id: 1 })
// 与该条sql语句的结果⼀致:delete from tbl_student where id = 1;
在egg框架中,访问get⽅式的接⼝没有CSRF漏洞,但是访问post/put/delet⽅式的接⼝有CSRF漏洞。
CSRF(Cross-site request forgery)简称: 跨站请求伪造,攻击者通过伪造⽤户的浏览器的请求,向访问⼀个⽤户⾃⼰曾经认证访问过的⽹站发送出去,使⽬标⽹站接收并误以为是⽤户的真实操作⽽去执⾏命令。攻击者利⽤⽹站对请求的验证漏洞⽽实现这样的攻击⾏为,⽹站能够确认请求来源于⽤户的浏览器,却不能验证请求是否源于⽤户的真实意愿下的操作⾏为。
造成的问题包括: 个⼈隐私泄露以及财产安全。
⽬前防御 CSRF 攻击主要有三种策略:
在项⽬中如果报csrf漏洞问题,⽬前咱们可以先关闭csrf漏洞检测,后期由前端携带token来解决:
// config.default.js
security: {
csrf: {
enable: false,
},
},

egg-jwt插件⽹站:https://www.npmjs.com/package/egg-jwt
$ npm install egg-jwt --save
plugins.js
jwt : {
enable: true,
package: 'egg-jwt',
},
config.default.js
jwt:{
secret:"888888"//加密秘钥
},


使⽤jwt进⾏⽤户身份认证⾮常⽅便。
此⽅法⽤来⽣成token信息,在登录接⼝中,验证⽤户身份通过之后,才去⽣成token。其语法格式如下:
// 签发token
this.app.jwt.sign(payload, secretOrPrivateKey, [options, callback])
// 示例
const token = 'Bearer ' + this.app.jwt.sign({ username: param.username},
this.app.config.jwt.secret,{ expiresIn: '60s' } );
参数说明
1.payload 为⼀个对象,后期可以根据token解析出这个对象的信息
2.secretOrPrivateKey 秘钥
3.options配置对象 {expiresIn : ‘60s’} token有效期
const token = this.ctx.get('Authorization').substring(7);
// 验证token,反编译获取username
const { username } = this.app.jwt.verify(token,this.app.config.jwt.secret)
Authorization =Bearer ${token} 。在需要身份认证的接⼝上添加jwt,需要在router.js中配置需要jwt认证的路由,添加了jwt认证的路由前端访问的时候,就需要在请求头部携带Authorization = Bearer ${token} 信息了。router.js
const { router, controller, jwt } = app;
// 给路由添加需要token认证
router.get('/student/findAll', jwt, controller.student.findAll);
router.post('/student/saveOrUpdate', jwt, controller.student.saveOrUpdate)
官⽹:https://swagger.io/
egg-swagger-docs:https://www.npmjs.com/package/egg-swagger-docs
$ npm install egg-swagger-docs --save
swaggerdoc : {
enable: true,
package: 'egg-swagger-docs',
},

swaggerdoc: {
dirScanner: "./app/controller", // 配置⾃动扫描的控制器路径
apiInfo: {
title: "xxx系统", // 接⼝⽂档标题
description: "xxx系统接⼝⽂档", // 接⼝⽂档描述
version: "1.0.0", // 接⼝⽂档版本
},
schemes: ["http", "https"], // 配置⽀持的协议
consumes: ["application/json","application/x-www-formurlencoded"], // 指定处理请求的提交内容类型(Content-Type)
produces: ["application/json"], // 指定返回的内容类型
securityDefinitions: { //安全认证
apikey: {
description: 'Authorization format: Bearer {token}', //描述
type: 'apiKey', //类型
name: 'Authorization', // 名称
in: 'header' //存在位置
}
},
enableSecurity: true, // 是否启⽤授权(默认false不启⽤)
// enableValidate: true, // 是否启动参数校验(默认true开启)
routerMap: false, // 是否⾃动⽣成路由(默认true开启)
enable: true,
},

完成插件引⼊之后,如果不修改默认配置,应⽤启动后,会⾃动扫描app/controller和app/contract下的⽂件。controller下的⽂件先不做描述。contract下的⽂件为定义好的请求体和响应体。
注意在app下创建contract⽂件夹,会扫描该⽂件夹,如果没有该⽂件夹启动项⽬会报错。- 注意在项⽬中留⼀个/路由以供swagger访问。
- 启动项⽬之后,可以使⽤http://127.0.0.1:7001/swagger-doc来访问doc⻚⾯, 使⽤http://127.0.0.1:7001/swagger-ui.html来访问swagger⻚⾯。


Swagger的⽂档内容编写在⾃⼰声明的Controller的⽂件中去编写。
@Controller {ControllerName}
/**
* @Controller StudentController:学生模块
*/
class StudentController extends Controller {
}

@Router {Mothod} {Path}class StudentController extends Controller {
/**
* @Router get /student/findAll
*/
// 查询接口处理函数
async findAll() {
};

@Summary {Summary} class StudentController extends Controller {
/**
* @Summary 查询学生模块
*/
// 查询接口处理函数
async findAll() {
};

@Description {Description}class StudentController extends Controller {
/**
* @Description 查询所有学生不需要传参
*/
// 查询接口处理函数
async findAll() {
};

@Request {Position} {Type} {Name} {Description}
body参数
如果写了@Request body,则可以在contract⽂件夹中声明请求体的模板。
controller/user.js:
class UserController extends Controller {
/**
* @Router post /user/login
* @Summary 用户登录模块
* @Description 传参username和password为必传参数
* @Request body loginVM
*/
// 登录接口
async login() {
}
contract/user.js
module.exports = {
// 对请求体参数模块
loginVM: {
username: {
type: "string",
require: true,
example: "admin"
},
password: {
type: "number",
require: true,
example: "123321"
}
}
}

query参数
必填项在名称前边加*
/**
* @Router delete /student/deleteById
* @Summary 通过id删除学⽣信息
* @Description 详细描述信息
* @Request query number *id 需要删除的id值
*/
async deleteById() {
...
}

多个query参数
/**
* @Router get /student/pageQuery
* @Summary 分⻚查询学⽣信息
* @Description 详细描述信息
* @Request query number page ⻚码
* @Request query number pageSize 每⻚条数
*/
async pageQuery() {
...
}

@apikey身份认证
如果在config中开启并定义了securityDefinitions,默认enableSecurity为false。则可
在注释块中加⼊@apikey,加⼊安全验证。也可定义成其他名字,只需@定义好的
字段名就好。关于securityDefinitions的定义可以⾃⾏搜索
swaggerdoc : {
securityDefinitions: {
apikey: { //这⾥的apikey代表注解的名称,如果这⾥改为apikey2,则可以使⽤@apikey2来携带身份信息
type: 'apiKey',
name: 'clientkey',
in: 'header',
},
},
enableSecurity: true,
},
如果egg项⽬内使⽤了egg-jwt做了身份认证,并在配置路由的时候给某个接⼝设置了jwt身份认证,那么需要使⽤该注解。否则有身份认证的接⼝在swagger中测试的时候,会报没有进⾏身份认证的错误。
{
"code": "credentials_required",
"message": "No authorization token was found"
}
路由注册:
const { router, controller, jwt } = app;
router.get('/student/findAll', jwt, controller.student.findAll);
路由@apikey注解:
/**
* @Router get /student/findAll
* @Summary 查询所有学⽣信息
* @Description 详细描述信息
* @apikey
*/
async findAll() {
...
}
npm install egg-cors --save
config/plugin.js
cors:{
enable: true,
package: 'egg-cors',
},

config/config.default.js
cors: {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
},
