Node系列文章目录
第一章:
Node.js与内置模块(fs文件系统、path路径模块、http服务器模块)
第二章:
Nodejs模块化(npm与包、开发自己的包、模块加载机制)
第三章:
Nodejs之Express(基本使用、Express路由)(一)
第四章:
Nodejs之Express( Express 中间件、中间件的分类、自定义中间件)(二)
第五章:
Nodejs之解决接口跨域问题
第六章:
Nodejs操作Mysql数据库
第七章:
Node之前后端身份认证(Session认证机制、JWT 认证机制)
第八章:
Nodejs之egg基本使用(初始化项目、内置对象、egg路由、egg控制器)
目录
一、egg服务
简单来说,Service 就是在复杂业务场景下⽤于做业务逻辑封装的⼀个抽象层,提供这个抽象有以下⼏个好处:
- 保持 Controller 中的逻辑更加简洁。
- 保持业务逻辑的独⽴性,抽象出来的 Service 可以被多个 Controller重复调⽤。
- 将逻辑和展现分离,更容易编写测试⽤例。
// 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
egg-mysql:
https://www.npmjs.com/package/egg-mysql
2.1 安装
$ npm install --save egg-mysql
2.2 配置
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,
}
};
2.3基本使用
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;
2.4 快捷⽅法
在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;
三、CSRF漏洞
在egg框架中,访问get⽅式的接⼝没有CSRF漏洞,但是访问post/put/delet⽅式的接⼝有CSRF漏洞。
CSRF(Cross-site request forgery)简称:
跨站请求伪造,攻击者通过伪造⽤户的浏览器的请求,向访问⼀个⽤户⾃⼰曾经认证访问过的⽹站发送出去,使⽬标⽹站接收并误以为是⽤户的真实操作⽽去执⾏命令。攻击者利⽤⽹站对请求的验证漏洞⽽实现这样的攻击⾏为,⽹站能够确认请求来源于⽤户的浏览器,却不能验证请求是否源于⽤户的真实意愿下的操作⾏为。
造成的问题包括:
个⼈隐私泄露以及财产安全。
-
防御CSRF攻击
⽬前防御 CSRF 攻击主要有三种策略:
- 验证 HTTP Referer 字段;
- 在请求地址中添加 token 并验证;
- 在 HTTP 头中⾃定义属性并验证。
在项⽬中如果报csrf漏洞问题,⽬前咱们可以先关闭csrf漏洞检测,后期由前端携带token来解决:
// config.default.js
security: {
csrf: {
enable: false,
},
},
四、egg-jwt
egg-jwt插件⽹站:
https://www.npmjs.com/package/egg-jwt
4.1 安装
$ npm install egg-jwt --save
4.2 配置
plugins.js
jwt : {
enable: true,
package: 'egg-jwt',
},
config.default.js
jwt:{
secret:"888888"//加密秘钥
},
3.3 使⽤
使⽤jwt进⾏⽤户身份认证⾮常⽅便。
-
sign()
此⽅法⽤来⽣成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有效期
-
verify()
此⽅法⽤来核实token,可通过此⽅法获取token上携带的头部的信息可使⽤如下代码来通过token获取⽤户信息:
const token = this.ctx.get('Authorization').substring(7);
// 验证token,反编译获取username
const { username } = this.app.jwt.verify(token,this.app.config.jwt.secret)
-
路由添加jwt身份认证
添加了jwt身份认证的接⼝在访问时,请求时在header中配置
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)
五、Swagger
官⽹:
https://swagger.io/
egg-swagger-docs:
https://www.npmjs.com/package/egg-swagger-docs
5.1 安装
$ npm install egg-swagger-docs --save
5.2 配置
-
config/plugin.js内添加如下插件信息
swaggerdoc : {
enable: true,
package: 'egg-swagger-docs',
},
-
config/config.default.js ⽂件内的userConfig对象内添加如下信息
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⻚⾯。
5.3 编写Swagger
Swagger的⽂档内容编写在⾃⼰声明的Controller的⽂件中去编写。
-
@Controller
在类上⽅使⽤此注解。
格式:
@Controller {ControllerName}
- 如果⽂件第⼀个注释块中存在标签@Controller,应⽤会扫描当前⽂件下的所有注释块,否则扫描将会跳过该⽂件。
- 如果不标示ControllerName,程序会将当前⽂件的⽂件名作为ControllerName。
/** * @Controller StudentController:学生模块 */ class StudentController extends Controller { }
-
@Router
在Controller类内的⽅法上⽅使⽤此注解。
格式:
@Router {Mothod} {Path}
Mothod:请求的⽅法(post/get/put/delete等),不区分⼤⼩写。
Path:请求的路由。class StudentController extends Controller { /** * @Router get /student/findAll */ // 查询接口处理函数 async findAll() { };
-
@Summary
在Controller类内的⽅法上⽅使⽤此注解。
格式:
@Summary {Summary}
接⼝信息⼩标题
class StudentController extends Controller {
/**
* @Summary 查询学生模块
*/
// 查询接口处理函数
async findAll() {
};
-
@Description
在Controller类内的⽅法上⽅使⽤此注解。
格式:
@Description {Description}
接⼝具体描述
class StudentController extends Controller {
/**
* @Description 查询所有学生不需要传参
*/
// 查询接口处理函数
async findAll() {
};
-
@Request
在Controller类内的⽅法上⽅使⽤此注解。
格式:
@Request {Position} {Type} {Name} {Description}
- position:参数的位置,该值可以是body/path/query/header/formData。
- Type:参数类型,body之外位置⽬前只⽀持基础类型,integer/string/boolean/number,及基础类型构成的数组,body中则⽀持contract中定义的类型。如果position是formData还将⽀持file类型。
- Name 参数名称.如果参数名称以*开头则表示必要,否则⾮必要。
-
Description:参数描述
如果你想给query或者path的参数设置example,你可以在Description前添加
以’eg:’开头的参数,实例如下 @Request query string contactId eg:200032234567
顾问ID
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() {
...
}
六、Cors
egg-cors
:
https://www.npmjs.com/package/egg-cors
1.安装
npm install egg-cors --save
2.配置
config/plugin.js
cors:{
enable: true,
package: 'egg-cors',
},
config/config.default.js
cors: {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
},