当前位置: 首页 > 工具软件 > Eggjs > 使用案例 >

web前端学习(四):基于koa的EggJs框架,优雅而又完美的Nodejs框架

柳培
2023-12-01

**前言: **

  • Egg.js为企业级开发应用,而产生的一门Node.js框架

  • 它使用模块化渐进式开发,内置多进程管理(Node是单进程,无法使用多核CPU的能力),具有高度可扩展的插件机制。

  • 基于Koa开发,性能优异, 框架稳定,测试覆盖率高

1.安装项目

1.安装项目

1.安装模块
 
    mkdir egg_demo                              1.建立项目文件夹

    yarn create egg  --type=simple              2.创建简单类型的项目
    yarn install(或者yarn)                       3.安装项目依赖的包
    
2.开启项目
    yarn dev                      4.开启项目(默认7001端口)
    yarn start                    5.项目正常运行(但有运行时占用cpu非常大)
  • yarn 会自动查找模块,所以不需要install,一般也不需要global。 (安装的比较慢,可能需要等待3-4分钟)

  • yarn start 会在后台开启一个服务,关闭需要使用 yarn stop 结束。 用于项目 上线

  • yarn dev 可以对当前的变化进行更新。

  • 如果程序在开发时出现线程断开的情况,需要首先考虑 plugin.js和config.default.js 的配置正确与否

2.项目结构

 
egg-example
├── app                       应用程序
│   ├── controller            控制器
│   │   └── home.js
│   ├── public                公有路径
│   ├── service               服务层
│   ├── middleware            中间件
│   ├── view                  视图文件
│   └── router.js             路由设置
├── config                    配置文件
│   └── config.default.js
└── package.json

2.router路由

1.封装router文件

(1. 在当前位置引入全局应用对象, app/router.js 的对象内添加以下内容

 require('./router/default')(app)        // 导入模块, 需要添加全局应用对象

(2.新建立router路由文件夹, 创建default.js

module.exports = app=>{ 
    // 单独分离出的路由
    const {router,controller} = app
    router.get('/default/index',controller.default.home.index)
}

3.controller控制器

1.创建控制器

yarn dev 功能可以对当前的请示结果进行分析。

函数内部必须使用async修饰, 在controller文件夹下新建“mini.js”文件,并填写以下内容。

'use strict';

const Controller = require('egg').Controller;

class MinicatController extends Controller {
  async index() {
    const { ctx } = this; // 获取当前的上下文对象
    ctx.body = '<h1>I am a MiniCat</h1>';
  }
  async getGirls() {
    const { ctx } = this;
    await new Promise(resolve => { // 异步单元测试, 等待异步请求结果
      setTimeout(() => {
        resolve(ctx.body = '<h1>小妹向你走过去啦!</h1>');
      });
    });
  }
}

module.exports = MinicatController;

2.测试控制器

向router添加路由

  router.get('/mini', controller.minicat.index); // 添加路由

在test/app/controller 添加 minicat.test.js

'use strict';

const { app } = require('egg-mock/bootstrap');

describe('minicat test', () => {
  it('minicat index', () => {
    return app.httpRequest()
      .get('/mini')
      .expect(200)
      .expect('<h1>I am a MiniCat</h1>');
  });
  it('minicat getGirls', () => {
    return app.httpRequest()
      .get('/getGirls')
      .expect(200)
      .expect('<h1>小妹向你走过去啦!</h1>');
  });
});

4.service 服务层

Service就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。就是把业务逻辑代码进一步细化和分类,所以和数据库交互的代码都放到Service中。这样作有三个明显的好处。

  • 保持Controller中的逻辑更加简介。
  • 保持业务逻辑的独立性,抽象出来的Service可以被多个Controller调用。
  • 将逻辑和展现分离,更容易编写测试用例。

1.创建service服务层

在app下新建立minicat.js文件,并填写以下内容

'use strict';
const Service = require("egg").Service;

class MinicatService extends Service{
    // Service方法也是异步方法
    async getGirl3(id){
        //因为没有真实连接数据库,所以模拟数据
        return {
            id:id,
            name:'小红',
            age:18
        }

    }
}

module.exports =MinicatService;

2.控制器与服务的交互

// // service数据层
  async getGirls3(){
    const {ctx} = this;      // 向服务层传入数据
    const res = await ctx.service.minicat.getGirl3(ctx.query.id);
    ctx.body = res 
  }

5.middleware中间件

中间件的作用域是全局,访问不同的页面, 只要添加ctx.session.xxx都会有效 。

1.配置中间件

在config/config.default.js 添加下面的内容

  /* 配置中间件 */
  config.middleware = [ 'counter' ];

2.创建中间件

官方要求中间件保存在 app/middleware 文件夹下。 其中,middleware是新创建的。

module.exports = options =>{
    return async (ctx,next) =>{
        if(ctx.session.counter){
            ctx.session.counter++;
        }else{
            ctx.session.counter=1;
        }
        await next();      // 异步等待下一个方法或者页面,防止页面卡死
    }
}

3.路由使用的中间件

在router.js中定义 counter 中间件。

每次访问路由都会执行一次添加,可以认为它是路由访问前,进行一定计数。

只要是调用ctx.session.counter, 次数都会加 1. 但如果路由也使用了这个中间件,则会加 2。

 //  启动router中间件
  const counter = app.middleware.counter() 
  router.post('/add1', controller.minicat.add1);
  router.post('/show1',counter, controller.minicat.show1);
  

6.schedule定时任务

官方要求 ,定时任务需要按照Egg的约定,/app目录下,新建shedule文件夹。然后再创建getTime.js文件

作用: 在一定的时间间隔内,对当前的配置进行设置。

创建后定时任务,重启项目后, 即可正常运行

const Subscription = require('egg').Subscription
 
class GetTime extends Subscription{
    static get schedule(){
        return {
            // interval:'3s',          // 间隔时间3s
            cron: '*/3 * * * * *',    // 间隔时间3s
            type:'worker'     // 类型
        }
    }
    async subscribe(){         // 方法会自动执行, 一般会去检查配置
        console.log(Date.now())
    }
}

module.exports = GetTime;    // 暴露这个类中的所有方法

// module.exports = {         // 只是暴露这个类。
//     GetTime:GetTime
// }; 

// export default GetTime;    // es6 暴露方法无效


*    *    *    *    *    *
┬    ┬    ┬    ┬    ┬    ┬
│    │    │    │    │    |
│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)
│    │    │    │    └───── month (1 - 12)
│    │    │    └────────── day of month (1 - 31)
│    │    └─────────────── hour (0 - 23)
│    └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, optional)

7.public静态资源文件

在public目录下添加 default.css 样式文件

则可以在view模板文件中直接找到


<link rel="stylesheet" href="/public/default.css"> 

直接访问public下的静态资源:(这是需要添加public路径的框架,但django的static也是需要添加到访问路径当中)


http://localhost:7001/public/assets/file/38e25517-3981-4987-aaf1-8c6c830ea097.jpg

控制器中使用视图文件

async getPage(){ 
   render('hello.html')
}

8.获取数据方法

1.获取Request数据

__get方法
   
    // 自由传参
    const username = ctx.query.username;

    // 严格传参(path路径)
    const params = ctx.params.name
    
__post方法
     >>>>>  注意:egg.js的post方法只遵循标准的x-www-form-urlencoded方式。 form-data不可以
    // post请求数据,是保存的请求包体中。使用ctx.request.body
   const title  = ctx.request.body.title,
    
__headers请求头数据
    // 获取token
	const token = ctx.request.headers.token
	
__url, method请求数据
	const url = ctx.request.url /method
	
__file文件数据
  const req = ctx.request.files; 
  let temp  = req[0].filepath  //临时文件路径
  
	
__response响应数据 
	ctx.body = { msg:"hello"}
	ctx.body = "测试成功"  // ctx.body响应结果

2.获取MySQL数据

1.基本操作


// 1.插入数据
const data = {
  title: 'Hello World',
  username:"xxx",
  password:"xxxx"
}
res = await app.mysql.insert(tTable,data);    // res2.insertId  可以查看插入的数据
  
  
// 2.删除数据
const data = {
  id:123   // 根据目标id删除
}
res = await  app.mysql.delete(tTable,data );
  
  
// 3.更新数据
const data = {
  // id:123  // 根据目标id更新
  username:'xxx',
  password:'xxxx' //新数据
}
const options = {
    where: {
      custom_id: 456
    }
};
res = await  app.mysql.update(tTable,data, options); 
  
  
      
// 4.查询数据 
const  options = { 
  where: { id: '23',name:'小明'  }, 
  columns: ['author', 'title'],  
  orders: [['created_at','desc'], ['id','desc']], 
  limit: 10,  
  offset: 0,  
})
// select默认会获取所有数据
res = await  app.mysql.select(tTable, options); 
// get 查询第一条符合条件的数据 
const post = await this.app.mysql.get(tTable, options);
// query方法执行原生sql语句
res = await app.mysql.query(`select * from ${tTable} where  ${new_params} `) 


2.高级操作


1.事务处理: 
// 一般用于两种关联事务的处理。
const conn = await this.app.mysql.beginTransaction(); try {
  await conn.insert('user', { 'username': 'hahahh','password':'xxx' }); 
  const row = { id: 8,username: '王麻子'};
  await conn.update('user', row);
  await conn.commit();
} catch (err) {
  await conn.rollback(); // rollback call won't throw err
  throw err; 
}      
 

**注意: **

1.查询的表不能是有 ‘-'的表,否则查询失败,

2.eggjs支持post请求中的x-www-form-urlencoded格式,form-data格式 不支持

3.编码规范,注意在使用query方法前,对sql语句用unescape进行解码。

4.where, set , values 中的参数值需要是使用引号包含

**5.where 中的参数用and连接,set中的参数用逗号’,'连接 , values中的参数逗号‘,’连接,并用括号“()”包裹 **

6.目前eggjs的mysql中query方法,使用js还是无法生成符合这个方法的日期格式数据。

mysql语句专用的日期格式的 SQL函数: current_timestamp() , 获取当前时间,不需要引号包裹

7. 插入数据的日期格式就是字符串,使用SQL函数没有效果

// 保存到数据库的时间
// (1.mysql语句专用的日期格式的 SQL函数:
current_timestamp()                  // 获取当前时间,不需要引号包裹
// (2.获取当前日期的格式化数据
const mysqlDateType = (isvalues=false)=>{
    // return (new Date()).toLocaleString()// 下午会出现异常.replace(/[\上\午\下\午]+?/gi,'')
    if(isvalues){
      // 插入数据的日期格式(自定义的格式化日期函数)
       return DateFormat(new Date(),'YYYY-MM-DD HH:mm:ss');   
    }else{ 
        return 'current_timestamp()';                     // 获取当前日期的SQL函数
    }
}

3.封装Models模型层

egg-mysql详情

-------- controller控制器中 
      // 初始化,获取服务层的路径  
      AdminDataindex = this.ctx.service.admin.dataindex
      HomeDataindex = this.ctx.service.home.dataindex 


-------- services服务层中

		// 执行函数,返回一个模型对象
		const User = require("service/models/User")()

		// 1.高级获取所有的字段
		// sql查询语句, 选择的条件可以是一个数组,但要经过模板字符串转换
        const sql = `select ${[...User.fillable,...User.hidden]} from ${User.table}`
        // 获取单独的字段
        const sql = `select ${User.fillable} from ${User.table}`
        
        // 2.更新部分字段
        const sql = `update user_msg set sex='女',token='${token}' where id='${user.id}'`
        const res = await ctx.app.mysql.query(sql)
               
        // 3.插入新数据 
        // 插入默认排序数据 
        const sql = `insert into user_msg values(NULL,"11","222")`
        // 插入部分数据
        const sql = `insert into user_msg(id,username,token) values(NULL,"11","222")`
        const res = await ctx.app.mysql.query(sql)
        // 数据 + 模板字符串,可以生成元组数据,
        //(即数据展开状态,但模板字符串是不支持直接使用扩展运算符,将数组展开)
        const sql =  `insert into ${table}(${keys}) values(${t_values})`; 
        
------ User.js模型层中
		'use strict';

        function User(){
            return { 
                // 使用的表
                table : 'user_msg',
                // 主键
                primaryKey : 'id', 
                // 排除的字段
                hidden : [
                    'password',
                    'token'
                ] ,
                // 可使用的字段
                fillable :[
                    'username', 
                    'sex',
                    'address', 
                    'registerTime',
                    'updateTime',
                    'deleteTime',
                    'isDelete',
                ]
            }
        }

        module.exports = User;

4.get,post请求方法

(1.get方法

控制器中编写方法

  // 自由传参模式, query
  // 则可以选择性添加参数 
  async getGirls() {
    const { ctx } = this;
    ctx.body = ctx.query.username;
  }

  // 严格传参模式  parmas
  // 则必需添加相应的参数
  async getGirls2(){
      const {ctx} = this; 
      ctx.body = ctx.params.name
  }
  

设置路由

  
  router.get('/getGirls/', controller.minicat.getGirls); // 添加路由
  router.get('/getGirls2/:name', controller.minicat.getGirls2); // 添加路由
  

请求方式

http://127.0.0.1:7001/getGirls?username=111

http://127.0.0.1:7001/getGirls2/20

(2.post方法

CSRF的全名为 Cross-site request forgery, 它的中文名为 伪造跨站请求。

注意:一定要x-www-form-urlencoded方式,form-data格式不可以

  1. 使用Rest-client插件 , 在项目下添加一个 test.http
POST http://127.0.0.1:7001/add
Content-Type: application/x-www-form-urlencoded

name = minicat

或者使用JSon格式,注意空行
{
   "name":"hello"
}
  1. 关闭csrf 安全策略

    在config/config.default.js 下添加 以下配置项, 然后点击

  2. 接收post请求参数 和路由配置

  // POSt请求
  async add(){
      const {ctx} = this;
      let  data = ctx.request.body        // 获取数据包中的参数
      ctx.body = { satus:200,  data:data }
  }
 // post方法
  router.post('/add', controller.minicat.add); 

9.模块的使用

1.egg-mysql数据库

安装egg-mysql数据库

yarn add egg-mysql

添加插件配置

在config/plugins.js添加以下内容,

/* egg-mysql */
exports.mysql = {
   enable:true,
   package:'egg-mysql'
}

在config/config.default.js 添加以下内容

 /* 安装mysql插件 */
    config.mysql = {
      app:true,      // 是否挂载到app下
      agent:false,   // 是否挂载到代理下
      client:{
        host:'127.0.0.1',   // 主机地址
        port:'3306',
        user:'root',
        password:'xxxx',
        database:'test-egg'   // 数据库名称
      }
    }

然后创建 utf8mb4 编码格式的数据库 , 名称为test-egg, 建立grils的表

const res = await app.mysql.insert('girls',data);           // 添加
const res= await app.mysql.select('girls',option)           // 查询 ,会获取所有数据 
const res= await app.mysql.update('girls',data,option)      // 更新
const res= await app.mysql.delete('girls',data)             // 删除   
const res = app.mysql.query('select * from girls where id= ?',[params.id])   // 执行sql语句

注意:

  • service服务中的数据库操作,必须使用异常捕获,以保证程序的正常运行。

  • 它们也都是在严格环境下,使用CommonJS 引入包的形式开发。

    ‘use strict’; require (“egg”)

  • 自定义创建的类是从egg 包中继承相应 类, 并把类中的方法全部暴露。 module.exports = XXXService

  • service和controller中,类里面的方法都是异步方法。即使用async修饰方法, await异步等待。

控制器从service获取数据

注意: 实际上表单上的数据没有Number数字类型,都是字符串。因此,使用字符串查询对应的数据,也可以得到结果。

const params = {id:3 , name:'小白', age:20, skill: '唱歌'} 
const res = await ctx.service.testdb.getGirl({id:ctx.query.id}  )
const res = await ctx.service.testdb.addGril(params)
const res = await ctx.service.testdb.delGril({id:ctx.query.id}  )      

2.egg-cors跨域

安装模块

yarn add egg-cors

配置信息 在config/plugins.js添加以下内容

exports.cors = {
  enable:true,
  package:'egg-cors'
}

在 config/config.default.js 添加以下内容

/* cors跨域 */
config.security = {
   csrf: {enable: true},
   domainWhiteList: [ 'http://localhost:3000','http://localhost:3001' ]  // 白名单
};
config.cors = {
    origin: ctx => ctx.get('origin'), //这种方式是允许所有的IP+端口跨域访问
    // origin: 'http://localhost:3000',  // 
    credentials: true,  // 开启认证
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
};

3.egg-jwt身份认证

安装模块

yarn add egg-jwt

配置信息 在config/plugins.js添加以下内容

exports.cors = {
  enable:true,
  package:'egg-jwt'
}

在 config/config.default.js 添加以下内容

 /*jwt认证*/
   config.jwt = {
       secret: 'AB12XY',  // 不能设置为123456
       enable: true, // default is false
       match: '/jwt', // optional
       expiresIn: '24h',
   }

middleware中间件使用方法:

'use strict';

module.exports = () => {
    return async function jwtErr(ctx, next) {
        const headerStr = ctx.request.header.access_token;

        if (headerStr) {
            try {
                console.log(headerStr,ctx.app.config.jwt.secret)
                const options = {

                    secret: ctx.app.config.jwt.secret
                }
                // 解码token,需传加密时的 secret
                const decode = await ctx.app.jwt.verify(headerStr, ctx.app.config.jwt.secret);
                ctx.state.user = decode; // 信息存一下,这步很重要,业务里要用
                await next();
            } catch (error) {

                ctx.status = 401;
                // 翻译错误码
                let message = error.message;
                ctx.body = {
                    code: -1,
                    msg: message,
                };
                return;
            }
        } else {
            ctx.status = 401;
            ctx.body = {
                code: -1,
                msg: '没有token,无法访问',
            };
            return;
        }
    };
};

controller控制器中使用方法:

  // 登录帐号,(返回token)
  async login() {
    const {ctx, app} = this;
    const req = ctx.request.body;

    // 空值验证
    const errArr = app.validator.validate({
      username: 'string',
      password: 'string',
    }, req);
    if (errArr) {
      ctx.body = { error:JSON.stringify(errArr), code:403   }
      return;
    }

    let responseData = null;
    try {
      const params = {
        where:{
            username:req.username,
            password:req.password,
        }
      }

      // 查询是否存在当前用户
      const data =await app.mysql.select('user-msg',params);
      console.log(data,data.length,data.length > 0)
      if (data.length > 0) {
        const user = data[0];
        const {jwt: {secret, expiresIn}} = app.config;
        // 需传 secret 过期时间
        const token = app.jwt.sign({
          data:{
            username: user.username,
            password: user.password,
          },
        }, secret, {
          expiresIn,
        });

        // 更新token
        const params = {  id:user.id, token:token }
        const res = await app.mysql.update('user-msg',params)
        responseData = {  msg:'登录成功!',  token:token, userinfo:user, code:200 }
      } else {
        responseData = { msg:'登录失败', error:'用户名或密码不正确',  code:403 }
      }
    } catch (err) {
      responseData = { msg:"出现异常!",  error:err, code:500  }
    }
    ctx.body = responseData

  }


4.egg-validate 验证器

安装模块

yarn add egg-validate

配置信息 在config/plugins.js添加以下内容

exports.cors = {
  enable:true,
  package:'egg-validate'
}

在 config/config.default.js 添加以下内容

  /**验证参数 */
  exports.validate = {
    // convert: false,
    // validateRoot: false,
  };

controller中验证使用

// 空值验证
const errArr = app.validator.validate({
  username: 'string',
  password: 'string',
}, req);
if (errArr) {
  ctx.body = { error:JSON.stringify(errArr), code:403   }
  return;
}

5.bcryptjs密码加密

安装模块

yarn add bcryptjs

配置信息 在config/plugins.js添加以下内容

exports.cors = {
  enable:true,
  package:'bcryptjs'
}

使用方法

引入模块
var bcrypt = require("bcryptjs");
var key = bcrypt.genSaltSync(10);  // 设置加密密钥

var hashPwd = bcrypt.hashSync("123",key)  
var res = bcrypt.compareSync("123",hashPwd)   // true

6.file文件保存(框架)

egg接收文件上传_魔笛Love的博客-CSDN博客_ctx.multipart()

首先在config.default.js中配置file文件接收设置,注意在assets下创建一个temp用户保存临时图片的文件夹

  /* file文件接收配置 */
  config.multipart = { 
    mode: 'file',
    tmpdir:  './app/public/assets/temp',
    cleanSchedule: {
      cron: '0 0 4 * * *',
    },
    fileSize: '100mb',
    whitelist() {
      return true;
    }, 
  }; 

接收上传的文件

  async avatarUpdate() {
    const {ctx, app} = this;
    const req = ctx.request.files; 
      
    // 临时文件的位置
    let res = utils.saveFile(req[0].filepath)  
    let responseData = null;
    if(res)
      responseData = { msg:'提交成功!',data:res,  code:200 } 
    else 
      responseData = { msg:'提交失败!',  code:403 } 
    ctx.body = responseData
  }

保存文件的模块


// (9.保存file文件
const saveFile = (file)=>{
    try{ 
            let date = Date.now() 
            let last = Math.floor(Math.random()*(99999-10000+1)+10000)
            let filename = date+'.'+last+'.jpg'
            let dirname = `/assets/file/${filename}` 
            // 注意把临时文件的“\”反斜杠转换成"/"下斜杠
            let fileData = file.replace(/\\/gi,'\/')
            // 当前的运行路径是项目的根目录 
            let saveDirname = 'app/public'+dirname
            console.log("当前写入的位置是",saveDirname)
            // // 方法一:分块写入(错误)
            // var ws = fs.createWriteStream (saveDirname);
            // ws.on('open', ()=> {
            //     const blockSize = 128;
            //     const nbBlocks = Math.ceil(fileData.length / (blockSize));
            //     for (let i = 0; i < nbBlocks; i += 1) {
            //         const currentBlock = fileData.slice(blockSize * i,
            //         Math.min(blockSize * (i + 1), fileData.length),);
            //         ws.write(currentBlock);
            //     } 
            //     ws.end();
            // });  
            // ws.on('error',(err)=>{console.log(err);reject(null);})
            // ws.on('finish',()=>{resolve(dirname)})

 
            // // 方法二:边读边写 (读写只能读一半)
            // var rs = fs.createReadStream (fileData );              //创建一个可读流
            // var ws =fs.createWriteStream( saveDirname); 
            // //如果要读取一个可读流中的数据,必须要为可读流绑定一个data事件,data事件绑定完毕,它会自动开始读取数据
            
            //     rs.once ( "open" ,function () {});
            //     rs. once ( "close" , function (){});
            //     ws.once ( "open" ,function () {   });
            //     ws. once ( "close" , function (){
            //         rs.end();
            //         ws.end();
            //     });
            // rs.on ( "data",function (data) {
            //     console.log (data) ;
            //     ws.write(data);
            // }); 
            // // pipe ( )可以将可读流中的内容,直接输出到可写流中
            // rs.pipe (ws) ; 


            // 方法三:复制临时文件
            fs.copyFile(fileData, saveDirname,(err)=>{
                if(!err){
                console.log('复制成功');
                }
            }) 
            return dirname   
    }catch(e){
        console.log("error is",e)
        return null
    }
}

7.jsonwebtoken认证(?)

express中使用jsonwebtoken

在service目录下,新建token.js文件:

'use strict';
 
const Service = require('egg').Service;
const jwt = require('jsonwebtoken');
 
class TokenService extends Service {
  async signJwt(_id) {
    return jwt.sign({
      data: {
        _id,
      },
      exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 7),
    }, 'this is secert key, you can replace it!');
  }
 
  async decodeJwt(token) {
    try {
      return jwt.verify(token, 'this is secert key, you can replace it!');
    } catch (err) {
      return err;
    }
  }
}
 
module.exports = TokenService;

10. view视图EJS模板引擎

服务端渲染的好处

  • 对SEO非常友好,单页应用,比如Vue是到客户端才生成的。这种应用对于国内的搜索引擎是没办法爬取的,这样SEO就不会有好的结果。所以如果是官网、新闻网站、博客这些展示类、宣传类的网址,必须要使用服务端渲染技术。
  • 后端渲染是老牌开发模式,渲染性能也是得到一致认可的。在PHP时代,这种后端渲染的技术达到了顶峰。
  • 对前后端分离开发模式的补充,并不是所有的功能都可以实现前后端分离的。特别现在流行的中台系统,有很多一次登录,处处可用的原则。这时候就需要服务端渲染来帮忙。

EJS是什么

EJS是基于javascript的一种历史悠久的模板引擎,生态良好,性能稳定,得到普遍的认可以及广泛的使用。在php时代就已经是达到了顶峰。

安装与配置环境

yarn add egg-view-ejs

配置环境

config/config.default.js下添加以下内容

/*配置ejs */
    config.view = {
      mapping:{     /* 匹配文件 */
         '.html':'ejs'
      }
    }
    config.ejs = {
        
    }

config/plugins.js添加

exports.ejs = {
    enable:true,
    package:"egg-view-ejs"
}

注意: 首先需要把module.exports 注释, 否则无法暴露这信exports.ejs模块

1.render渲染模板

 // ejs模板引擎 
  async index1(){
    const {ctx} = this;
    await ctx.render('minicat.html',{
      id:2021,
      name:'minicat',
      age:"18",
      skills:['坚持','努力','沉着']
    })   
    // ctx.body = "hello"
      // render方法返回的是promise对象,所以需要使用异步
  }

2.<%%>模板语法

模块需要放在app/view下, 创建minicat.html ,并在html插入以下内容

// 1.include组合文件
<% include header.html %>               // 使用include加载文件,文件内只是一个标签

// 2.=赋值语句
<h2>现在是                               // 赋值
    <%=id %>
    <%=name %>
    <%=age %>
</h2>  

// 3.for循环遍历 
    <ul>
        <% for(var i=0;i<skills.length;i++){ %>      // for循环遍历
            <li><%=skills[i] %></li>
        <% } %>  
    </ul> 
    
 // 4.if..else条件判断
 <%if(islogin){ %
     <button>已登录</button>
 <%}else { %>
     <button>已登录</button>
 <%}%>
 
 
 // 5.<% %> 变量语句
 <% var name = "ejs模板" %>

静态css资源

根路径是在app, 只要是默认文件夹,就可以views , controller 直接找到 ”xxx.html 或者 xxx.js, “,

但是静态资源 public目录下,它并非默认文件夹,属于可修改配置。

    /* 修改ejs 配置*/
    config.ejs = {
      // delimiter: "$"     // 修改默认分隔符
    }

    /* 配置静态文件路径 */
    config.static = {
      // prefix:'/assets/'            //  如果使用,则需要把public 改成static
    }

在public目录下添加 default.css 样式文件

则可以在view模板文件中直接找到


<link rel="stylesheet" href="/public/default.css"> 

11.cookie 和 session存储

1.session的使用

(1.什么是session?

  • session和cookie非常类似 ,但session比cookie更加安全,因此session只允许在服务端进行操作。
  • session用于保存用户登录信息,和用户信息。而cookie用于保存是否登录 。 重要的信息放在session, 公开或者临时信息存在cookie, 。

(2.session的配置

config/config.default.js 下添加

 /* session的配置 */
    config.session = {
      key :"PANG_SESS",   // 设置Key的默认值
      httpOnly:true,      // 设置服务端操作
      maxAge:1000*60  ,   // 设置最大有效时间
      renew: true,        // 页面有访问动作自动刷新session, 一般用于是否需要是否登录
    }

(3.session的操作

注意:与cookie不一样的sessionl默认支持中文, 不需要加密与解密。

ctx.session.username =  "minicat"                 添加session
ctx.session.username = null                       删除session
const se = ctx.session.username                   获取session

2.cookie的使用

(1. 什么是cookie?

HTTP请求是无状态的,但是在开发时,有些情况是需要知道请求的人是谁的。为了解决这个问题,HTTP协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保留,并在下一次请求同一个服务的时候带上。

开发者选项的 ”网络导航项(network)“ 或者 ”应用(Application)“, 可以查看cookie的添加信息

**(2.cookie的配置 **

  1. **ctx.cookies.set() **

    • 第一个是key, 第二个是value, 第三个是配置项 ,这是一个对象的形式

    • maxAge: xxx 以毫秒计数

    • httpOnly:true 服务端专属修改cookie, 客户端不可以通过JS直接使用**document.cookie 获取cookie**,但对开发都选项的控制台无影响。

    • encrypt:true 允许使用中文设置cookie, 注意,使用ctx.cookie.get.获取数值需要添加这个配置项 ctx.cookies.get("user",{encrypt:true})

  2. ctx.cookies.get()

    • 当获取cookie的时候, 如果使用了中文添加到cookie值时,需要解密,否则返回的结果是undefined;

    • 第一个是key,第二是配置项,一般也添加 encrypt: true 来解密。

    • HttpOnly的设置:伪造Cookie来绕过登录是黑客经常使用的一种手段,所以为了安全,Egg.js默认设置只允许服务端来操作Cookie

(3.使用实例

视图页面minicat.html

    <div>
        <button onclick="add()" >增加Cookie</button>
        <button onclick="del()" >删除Cookie</button>
        <button onclick="editor()" >编辑Cookie</button>
        <button onclick="show()" >展示Cookie</button>
    </div>
    <script>
        function add(){
            // 原生请求数据的api
            fetch("/add",{
                method:'post',
                headers:{
                    'Content-type':"application/json"
                }
            })
        }
        function del(){
            fetch("/del",{
                method:'post',
                headers:{
                    'Content-type':"application/json"
                }
            })
        }
        function editor(){
            fetch("/editor",{
                method:'post',
                headers:{
                    'Content-type':"application/json"
                }
            })
        }
        function show(){
            //  fetch类似一个axios,但它是原生的api
            fetch("/show",{
                method:'post',
                headers:{
                    'Content-type':"application/json"
                }
            }).then(response=>{
                return response.json();     // 首先需要返回一个回调,promise对象
            }).then(data=>{                 // 再去获取响应结果
                console.log(data);
            }).catch(err=>{
                console.log("错误是",err)
            })
        }
    </script>


控制器 minicat.js

 // Cookie的设置
  async add(){                  // 添加cookie
    const ctx = this.ctx
    ctx.cookies.set("user",'加入minicat.com ',{
      maxAge:1000*60, 
      httpOnly:true,
      encrypt:true
    })
    ctx.body = {
      status:200,
      data:"Cookie添加成功"
    }
  }
  async del(){                    // 修改cookie
    const ctx = this.ctx
    ctx.cookies.set("user",'')
    ctx.body = {
      status:200,
      data:"Cookie删除成功"
    }
  } 
  async editor(){                  // 编辑cookie(略)
    const ctx = this.ctx
    ....
  } 
  async show(){                   // 获取cookie
    const ctx = this.ctx  
    ctx.body = {
      status:200,
      msg:"Cookie获取成功",
      data:ctx.cookies.get("user",{
        encrypt:true      // 返回结果前进行解密
      })
    }
  }

3.fetch的使用

1.什么是fetch?

  • fetch是浏览器提供的原生AJax接口

  • 由于原来 的XMLHttpRequest不符合分离原则,基于事件模型处理异步请求逐渐淘汰。

    Promise的处理具有更强大的优势。


    //  fetch类似一个axios,但它是原生的api
    fetch("/show",{
        method:'post',
        headers:{
        	'Content-type':"application/json"
        }
    }).then(response=>{
    	return response.json();     // 首先需要返回一个回调,promise对象
    }).then(data=>{                 // 再去获取响应结果
    	console.log(data);
    }).catch(err=>{
    	console.log("错误是",err)
    })

2.原生的ajax

var xhttp = new XMLHttpRequest();           // 创建xml对象
xhttp.onreadystatechange = function() {     // 设置响应函数
    if (this.readyState === 4 && this.status === 200) {
      console.log(this.responseText);
    }
};
xhr.onload = function() {
  console.log(xhr.response);
}; 
xhr.onerror = function() {
  console.log("Oops, error");
};
xhttp.open("GET", "/", true);              // 准备发送请求
xhttp.send();                              // 响应成功后,会触发相应的函数,执行回调函数

12. extend 扩展方法

官方要求扩展必须保存在 app/extend 文件夹下, extend是新创建的文件夹,

    
application   全局应用对象                              app对象            this.app
context       请求上下文                                ctx对象            this.ctx
request       请求级别的对象,提供了请求相关的属性和方法     ctx.request对象  this.ctx.request
response      请求级别的对象,提供了相应相关的属性和方法     ctx.response对象 this.ctx.response
helper        帮助函数                                 ctx.helper对象   this.ctx.helper

1.创建application扩展

1.创建application.js文件, 并添加以下内容

function getTime(){
    let  now = new Date();
    let year = now.getFullYear(); //得到年份
    let  month = now.getMonth()+1;//得到月份
    let date = now.getDate();//得到日期
    let  hour= now.getHours();//得到小时数
    let minute= now.getMinutes();//得到分钟数
    let second= now.getSeconds();//得到秒数
    let nowTime = year+'年'+month+'月'+date+'日 '+hour+':'+minute+':'+second;
    return nowTime;
}

module.exports = {     // 暴露模块
  
  // 方法的扩展     app.currentTime()已经成为app下的全局方法
    currentTime(){     
        const current = getTime();
        return current;
    },
    
    // 属性的扩展  app.timeProp  已经成为app下的全局属性、
    // 加入get,就会默认是一个属性,可以直接以属性的形式在`controller`方法里进行调用。
    get timeProp(){     
        return getTime();
    }
    
    // 属性的扩展 app.token
    set token(t){     
        let t = 'token';
    }
}

2.控制器调用扩展


  async getData(){          
    const {ctx , app} = this   
    let data = {
        nowtime:app.currentTime(),   // 调用自定义扩展的全局方法
        property:app.timeProp        // 调用自定义扩展的全局属性
        property2:app.get("timeProp")// 调用自定义扩展的全局属性
    } 
    
    app.token = "minicat.com";           //  获取属性, 也可以扩展为属性
    app.set("token","minicat.com")       //  设置属性, 也可以扩展为属性
  }

2. 创建context 扩展

1.创建一个context.js文件, 填写以下内容

module.exports = {
    // 作用: 将get方法的自由传参query和 post请求传参合并,可以自定义获取参数中的数据
    // 扩展ctx的方法
    myquery(key){                        // 方法扩展
        const method = this.request.method
        if(method == 'GET'){
            return key ? this.query[key] : this.query;
        }else{
            return key ? this.request.body[key] : this.request.body;
        }
    }
}

2.控制器调用扩展

  // 2.context
  async newContext(){
    const {ctx} = this ;  
    //   1.get请求
    //  自由传参   ctx.query.username        为空
    //  严格传参    ctx.params.name          不允许为空
    //   2.post请求
    //  获取数据包      ctx.request.body.name   为空
    const params = ctx.myquery()  // 默认为{},  使用扩展方法
    
  }

3. 创建request扩展

1.创建一个context.js文件, 填写以下内容

module.exports = {
    // 作用:获取请求头上的数据, 一般用户获取 请求头上的token  
    // 扩展ctx.request属性
    get token() {    
      return this.get("token");
    },
};

2.控制器调用扩展

  // 3.request
  async newRequest(){
     const {ctx} = this;
     const token = ctx.request.token;          //  自定义扩展属性1 
     const token = ctx.request.get("token");   //  自定义扩展属性2, 
     const method = ctx.request.method;         // 获取默认方法
     ctx.body = {
       status:200,
       body:token,
       method:method
     }
  }

4.创建response扩展

1.创建一个response.js文件, 填写以下内容

module.exports={
   // 作用: response 可以设置响应token 
   // 扩展属性
    set token(token){    // 设置响应token
      this.set('token',token)
    }
  };

2.控制器调用扩展


  async newResponse(){
    const {ctx} = this;
    ctx.response.token = "minicat.com";           // 自定义扩展属性1
    ctx.response.set("token","minicat.com")       // 自定义扩展属性2
    ctx.body = { status:200,  msg:'成功访问' }
 }

5.创建helper扩展

1.创建一个helper.js文件, 填写以下内容

module.exports = {
    base64Encode(str = '' ){
      return new Buffer(str).toString('base64');
    } 
  }


2.控制器调用扩展


 async newResponse(){
  const {ctx} = this; 

  const testBase64 = ctx.helper.base64Encode('jspang.com')  // 扩展方法,方法加密
  ctx.body = testBase64
}

6.ctx常用的扩展方法

1.请求类的方法
ctx.request                       获取请求的数据

ctx.request.header.xxx              获取请求头上的数据


2.响应类的方法
ctx.response                       获取请求的数据


3.中间件类的方法
ctx.app.mysql.xxx方法                执行数据库语句(异步方法)

ctx.app.jwt.xxx 方法                 执行安装的jwt中间件中的方法


4.数据库类的方法
ctx.service.home. xxx               获取service服务层文件夹下的文件,调用方法

7.config相关配置项

任何在config.webpack.js中添加的配置项
 config.xxx=  {   xxx :xxx }
 
都能在所有页面中访问到
const abc = app.config.xxx;

或者使用相关的方法
app.xxx(xxxx)


13.项目布署

1.引入模块

使用commonJS语法,只能通过 require 引入包

const qs = require("querystring")

自定义模块

module.exports = {
    // 音乐列表
    music_box:"music_box"
}

// 引入模块
const table = require("../contant.js")
// 使用模块
table.music_box

  1. 在nodejs中,一个包就相当于一个模块,即使暴露了文件内的模块,依旧还有文件这层。这与 es6不同,并且commonjs不支持es6语法引入模块。

  2. es6不用写后缀,但nodejs自定义的模块,就必需写后缀名, 这很大程度上讲,commonjs采用的是严格格式,对引入的文件格式要求比较高

2.项目根目录

不同的语言,取别名的方法有很大的差异, 当前模块适用于多数的webpack打包类型的框架。

安装模块: yarn add module-alias

(1.简单通用方法


----package.json中配置路径,添加下方内容

     "_moduleAliases": {
        "@root": ".",
        "@app": "app"
      },


---全局注册取别名模块, 内容添加以下内容(一般是项目的总配置文件)

    module.exports = appInfo => {
        ....
      /* 项目取别名 */
      require('module-alias/register')
    }


---控制器中使用
	const func = require("@app/public/utils/func.js")

(2.标准建议方法

修改package.json, 对项目的规范性开发处理的很不好,很容易导致重要文件被不经意间修改,所以建议新建立单独的文件,用于填写重命名规则。


---- 在config项目下添加 config-alias.js文件, 添加以下内容

    const moduleAlias = require('module-alias')
    // 路径取别名, 当前路径不是根路径,必需查找到上一级“..”
    moduleAlias.addAliases({
      '@root'  : __dirname+"/..",
      '@app': __dirname + '/../app', 
    })



---- 全局注册取别名模块, 内容添加以下内容

    module.exports = appInfo => {
        ....
      /* 项目取别名 */
      require('./config-alias.js')       // 新添加的自定义文件
      require('module-alias/register')
    }


---- 控制器使用
	const func = require("@app/public/utils/func.js")

3.项目布署


yarn dev        开发者模式运行
yarn start      生产模式运行

  • 使用yarn start会导致内存占用过高, 对数据库的依赖很大,占用cpu也非常多
  • 所以建议使用 yarn dev 开启 egg.js 项目,再考虑使用 mysql的缓存策略

4.学习总结

1.文件路径引用问题

app是根目录, config是插件配置目录, test是测试目录

关联链接: 技术胖-Egg.js快速入门 200分钟掌握企业级框架开发和应用

  • **controller**是官方创建目录, 一般被 路由调用 。

    (eg:controller.minicat.index)

  • **public**是官方创建目录 , 一般保存css,js等静态文件,但被view内的文件引用,需要采用根目录的形式。 (eg: /public/css/index.css

  • router.js 是官方创建文件, 一般是定义路由规则,以及与控制器、中间件的关系 。

    (eg: router.get("/index",controller.minicat.index), 使用 app.middleware.counter() )

  • service 是自定义官方建议目录, 一般被控制器调用,通过ctx 执行上下文获取路径和函数信息

    (eg: ctx.service.testdb.addGirl(params))

  • view是自定义官方建议目录, 一般由控制调用,用于返回一个html页面。 这个页面使用EJS模板引擎,将模板语法加入到html页面中。

    (eg: ctx.render('minicat.html',{ id:2021, name:'minicat' }

  • middleware是自定义官方建议目录, 一般由内部依赖的属性决定, 因为使用的是箭头函数,所以可以被整体引入和调用依赖属性 使用。

    (eg: ctx.session.counter

  • extend是自定义官方建议目录, 根据扩展目录, 只可以 被相应的对象调用。

    (eg: app.currentTime() 和 app.timeProp )

  • schedule是自定义官方建议目录, 重启服务,自动调用。

2.学习总结:

egg.js是一门具有弹性的框架,可以根据建设网站需要,自由选择是否使用内置配置以及使用其它扩展插件。丰富的生态圈,强大的性能值得我们去学习。

因为采用的是渐近式开发,它的体积虽然比其它node.js框架更加庞大,但它是以轻量化的形式面向开发者使用。基于内置多进程管理,对于系统的处理事件效率和运行的稳定性都是极大的提升。

 类似资料: