• Nodejs之egg基本使用(egg服务、egg-mysql、CSRF漏洞、egg-jwt、Swagger)


    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    服务的使⽤: 服务⼀般被controller使⽤。在controller中⽤ctx.services.student.findById(1)来访问service中的⽅法。
    如下完整例⼦
    app/router.js

    module.exports = ( app) =>{
    app.router.get('/student/findAll ', controller.student.findAll);
    };
    
    • 1
    • 2
    • 3

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    二、egg-mysql

    egg-mysql: https://www.npmjs.com/package/egg-mysql

    2.1 安装

    $ npm install --save egg-mysql
    
    • 1

    2.2 配置

    config/plugin.js

    'use strict';
    /** @type Egg.EggPlugin */
    module.exports = {
     mysql: {
     enable: true, package: 'egg-mysql',
     }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    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,
    		 }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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","男")
      
      • 1
      • 2
      • 3
    • get(table,{}) 查询
      table为表名,{}为where条件

      mysql.get('tbl_student', { id: 1 })
      // 与该条sql语句的结果⼀致:select * from tbl_student where id = 1;
      
      • 1
      • 2
    • 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;
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    • 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;
      */
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • delete(table,{}) 删除
      table为表名,{}为删除条件

      mysql.delete(tbl_student, { id: 1 })
      // 与该条sql语句的结果⼀致:delete from tbl_student where id = 1;
      
      • 1
      • 2

    三、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,
     },
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    四、egg-jwt

    egg-jwt插件⽹站:https://www.npmjs.com/package/egg-jwt

    4.1 安装

    $ npm install egg-jwt --save
    
    • 1

    4.2 配置

    plugins.js

    jwt : {
     enable: true,
     package: 'egg-jwt',
    },
    
    • 1
    • 2
    • 3
    • 4

    config.default.js

    jwt:{
     secret:"888888"//加密秘钥
    },
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    在这里插入图片描述

    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
    • 2
    • 3
    • 4
    • 5
    • 6

    参数说明
    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)
    
    • 1
    • 2
    • 3
    • 路由添加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)
    
    • 1
    • 2
    • 3
    • 4

    五、Swagger

    官⽹:https://swagger.io/
    egg-swagger-docs:https://www.npmjs.com/package/egg-swagger-docs

    5.1 安装

    $ npm install egg-swagger-docs --save
    
    • 1

    5.2 配置

    • config/plugin.js内添加如下插件信息
    swaggerdoc : {
     enable: true,
     package: 'egg-swagger-docs',
    },
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    • 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,
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    完成插件引⼊之后,如果不修改默认配置,应⽤启动后,会⾃动扫描app/controllerapp/contract下的⽂件。controller下的⽂件先不做描述。contract下的⽂件为定义好的请求体和响应体

    在这里插入图片描述
    在这里插入图片描述

    5.3 编写Swagger

    Swagger的⽂档内容编写在⾃⼰声明的Controller的⽂件中去编写。

    • @Controller
      在类上⽅使⽤此注解。
      格式:@Controller {ControllerName}
      • 如果⽂件第⼀个注释块中存在标签@Controller,应⽤会扫描当前⽂件下的所有注释块,否则扫描将会跳过该⽂件。
      • 如果不标示ControllerName,程序会将当前⽂件的⽂件名作为ControllerName。
      /**
       * @Controller StudentController:学生模块
       */
      class StudentController extends Controller {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    在这里插入图片描述

    • @Router
      在Controller类内的⽅法上⽅使⽤此注解。
      格式:@Router {Mothod} {Path}
      Mothod:请求的⽅法(post/get/put/delete等),不区分⼤⼩写。
      Path:请求的路由。
      class StudentController extends Controller {
          /**
           * @Router get /student/findAll
           */
          // 查询接口处理函数
          async findAll() {
          };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    在这里插入图片描述

    • @Summary
      在Controller类内的⽅法上⽅使⽤此注解。
      格式:@Summary {Summary}
      接⼝信息⼩标题
    	class StudentController extends Controller {
    	    /**
    	     * @Summary 查询学生模块
    	     */
    	    // 查询接口处理函数
    	    async findAll() {
    	    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    • @Description
      在Controller类内的⽅法上⽅使⽤此注解。
      格式:@Description {Description}
      接⼝具体描述
    class StudentController extends Controller {
        /**
         * @Description 查询所有学生不需要传参
         */
        // 查询接口处理函数
        async findAll() {
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    • @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() {
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    contract/user.js

    module.exports = {
        // 对请求体参数模块
        loginVM: {
            username: {
                type: "string",
                require: true,
                example: "admin"
            },
            password: {
                type: "number",
                require: true,
                example: "123321"
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    query参数
    必填项在名称前边加*

    /**
     * @Router delete /student/deleteById
     * @Summary 通过id删除学⽣信息
     * @Description 详细描述信息
     * @Request query number *id 需要删除的id值
     */
     async deleteById() {
     ...
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    多个query参数

    /**
     * @Router get /student/pageQuery
     * @Summary 分⻚查询学⽣信息
     * @Description 详细描述信息
     * @Request query number page ⻚码
     * @Request query number pageSize 每⻚条数
     */
    async pageQuery() {
     ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    @apikey身份认证
    如果在config中开启并定义了securityDefinitions,默认enableSecurity为false。则可
    在注释块中加⼊@apikey,加⼊安全验证。也可定义成其他名字,只需@定义好的
    字段名就好。关于securityDefinitions的定义可以⾃⾏搜索

    swaggerdoc : {
     securityDefinitions: {
     apikey: { //这⾥的apikey代表注解的名称,如果这⾥改为apikey2,则可以使⽤@apikey2来携带身份信息
    	 type: 'apiKey',
    	 name: 'clientkey',
    	 in: 'header',
     },
     },
     enableSecurity: true,
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果egg项⽬内使⽤了egg-jwt做了身份认证,并在配置路由的时候给某个接⼝设置了jwt身份认证,那么需要使⽤该注解。否则有身份认证的接⼝在swagger中测试的时候,会报没有进⾏身份认证的错误。

    {
     "code": "credentials_required",
     "message": "No authorization token was found"
    }
    
    • 1
    • 2
    • 3
    • 4

    路由注册:

    const { router, controller, jwt } = app;
    router.get('/student/findAll', jwt, controller.student.findAll);
    
    • 1
    • 2

    路由@apikey注解:

    /**
     * @Router get /student/findAll
     * @Summary 查询所有学⽣信息
     * @Description 详细描述信息
     * @apikey
     */
    async findAll() {
     ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    六、Cors

    egg-corshttps://www.npmjs.com/package/egg-cors

    1.安装

    npm install egg-cors --save
    
    • 1

    2.配置

    config/plugin.js

    cors:{
     enable: true,
     package: 'egg-cors',
    },
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    config/config.default.js

    cors: {
     origin: '*',
     allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
    },
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

  • 相关阅读:
    如何本地windows电脑通过scp下载vps服务器内的文件
    iceoryx之Roudi
    用C++或者Python解析gltf文件
    RK356X/RK3588构建Ubuntu20.04根文件系统
    HTML 学习笔记(七)列表
    杭电多校6 F - Maex (树形DP)
    leetcode148. 排序链表
    【Spring Cloud】新闻头条微服务项目:FreeMarker模板引擎实现文章静态页面生成
    备战23秋招,c/c++Linux后端开发岗(简历/技术面)分享
    docker-compose
  • 原文地址:https://blog.csdn.net/qq_48617322/article/details/127817201