【Node.js】egg.js的初体验,一些零散概念——模版引擎,cookie,session,接口请求接口,中间件,定义公共方法和属性,自定义插件

文章目录

  • 模版引擎
    • 安装与配置
    • 使用
    • 改变默认获取html文件路径
    • 引入静态资源
  • 使用cookie
  • session的操作
  • 接口请求接口
  • 中间件
  • 定义公共方法和属性
  • 自定义插件

模版引擎

就是能够使用html文件并且把处理好的html文件返回给浏览器,需要借助egg-view-ejs。

安装与配置

yarn add egg-view-ejs

config\plugin.js中登记:

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

然后再进行配置config\config.default.js:

config.view = {
  mapping: {
    ".html": "ejs"
  }
};

config.ejs = {
  
};

使用

然后创建html静态文件,例如app\view\user.html,然后在业务代码中:

await ctx.render('user.html'); // 替换掉返回body的内容,egg会自动去view文件夹找对应user.html的文件返回

如果想给html文件传入一些变量:

await ctx.render('user.html', { // 第二个参数写对象,传入一些变量
    id: 100
})

然后在html中使用变量:

<body>
  id: <%= id %> // 固定写法取变量

  <%# 这样写注释 %>
  
  // 写js语句比较麻烦
  <ul id="user">
      <% for(var i=0; i<lists.length; i++){ %> // lists是传入的一个数组
        <li>
          <%= lists[i] %>
        li>
      <% } %>
  ul>
body>

如果觉得<%= id %>写法不好,要换成<$= id $>,可以去config\config.default.js配置:

config.ejs = {
    delimiter: "$"
};

如果不想全局配置这个东西,可以单独配置

await ctx.render('user.html', { // 第二个参数写对象,传入一些变量
    id: 100
},{
    delimiter: '$' // 第三个参数写一些单独的配置
})

改变默认获取html文件路径

默认egg会从view文件夹中去读取html文件,如果想从其他文件夹中读取,可以在全局配置中更改config\config.default.js:

const path = require('path');
// ...

config.view = {
  mapping: {
    ".html": "ejs"
  },
  root: [
    path.join(appInfo.baseDir, "app/html"), // 多加个html文件夹,egg就会从这个文件夹里找
    path.join(appInfo.baseDir, "app/view") // 虽然是默认,但是当你添加了其他文件夹,默认的也要写上
  ].join(",")
};

引入静态资源

例如html/a.html:

<h1>a.htmlh1>

在html/index.html中要引入这个文件:

<body>
  <% include a.html %>
body>

其他的静态资源都放在assets文件夹里,然后利用默认安装的egg-static库去完成配置config\config.default.js:

config.static = {
  prefix: "/assets/",
  dir: path.join(appInfo.baseDir, "app/assets")
};

然后就可以引入静态资源了,例如html/index.html中

 ...
  <title>usertitle>
  <link rel="stylesheet" type="text/css" href="assets/css/user.css"/> // 正常引入
head>
<body>
  <img src="assets/img/f1.jpg" />

  <script src="assets/js/user.js">script>
...

使用cookie

模拟写一个登陆接口:

async login() {
    const { ctx } = this;
    const body = ctx.request.body;
    ctx.cookies.set("user", JSON.stringify(body), { // 设置cookie,返回给浏览器时,浏览器会自动写入cookie里
        maxAge: 1000 * 60 * 10, // 有效时间
        httpOnly: false, // cookie禁止前端修改
    });

    ctx.body = {
        status: 200,
        data: body
    };
}
    
// 路由配置就省略了

如果想在模板使用到cookie:

async index() { 
    const { ctx } = this; 
    
    // 设置中文内容的cookie方法一
    ctx.cookies.set("zh", "测试", { // 设置中文内容的cookie有个条件,就是一定要加密
        encrypt: true
    });
    const zh = ctx.cookies.get("zh", { // 获取要解密
        encrypt: true
    });
    // console.log(zh)
    
    // 设置中文内容的cookie方法二,用base64加解密
    ctx.cookies.set("base64", this.encode("中文base64"));
    const base64 = this.decode(ctx.cookies.get("base64"));

    // ctx.body = 'user index';
    const user = ctx.cookies.get("user"); // 获取指定cookies
    await ctx.render('user.html', {
        user: user ? JSON.parse(user) : null, // 传入模版
        zh,
        base64
    });
}

修改cookie,举例登陆退出接口:

async logout() {
    const { ctx } = this;
    ctx.cookies.set("user", null);

    ctx.body = {
        status: 200
    };
}
// 路由配置就省略了

session的操作

在接口中

// 添加,添加的session会默认存到浏览器的cookie字段中
ctx.session.user = body;
ctx.session.zh = "中文也可以";
ctx.session.test = "test";

//清除session
ctx.session.user = null;

// 获取session
const session = ctx.session.user;
const zhSession = ctx.session.zh;

在config\config.default.js中可设置

config.session = {
	 key: "MUKE_SESS", // 设置存在cookie时,key的名字
	 httpOnly: true, // 只能在服务端修改
	 maxAge: 1000 * 50, // 生效时间
	 renew: true // 每次刷新页面时都会重置过期时间
};

如果后面session越来越多,cookie就存不下了,这时候把session存在服务器内存中,cookie只对其保存服务器session的引用地址就可以了,创建根目录/app.js

module.exports = app => {
  const store = {};
  app.sessionStore = { // 就这样设置,底层就会帮我们存在内存中
    async get(key){
      console.log("--store--", store)
      return store[key];
    },
    async set(key, value, maxAge){
      store[key] = value;
    },
    async destroy(key){
      store[key] = null;
    }
  };
}

接口请求接口

async curlGet() {  // 调用get接口
    const { ctx, app } = this;
    const res = await ctx.curl("http://localhost:7001/", {
        dataType: "json" // 如果接口返回的只是字符串,那么就写 dataType: "text"
    });
    console.log(res);
    ctx.body = {
        status: 200,
        data: res.data // 获取接口返回的data
    };
}

async curlPost() {
    const { ctx } = this;
    const res = await ctx.curl("http://localhost:7001/login", {
        method: "post",
        contentType: "json",
        data: ctx.request.body, // 请求的参数
        dataType: "json"
    });
    console.log(res);
    ctx.body = res.data; // res.data这里包含了 status: 200
}

// 路由配置就不记录了

中间件

中间件就是当有接口请求的时候,会经过的地方,我们可以在这里做一些事情。egg规定app/middleware文件夹里面放中间件。例如middleware/m1.js:

module.exports = options => {
  return async (ctx, next) => {
    console.log('m1 start');
    await next(); // 这里相当于把上下隔开,上面是入口,下面是出口
    console.log('m1 end');
  }
}

middleware/m2.js:

module.exports = options => {
  return async (ctx, next) => {
    console.log('m2 start');
    await next();
    console.log('m2 end');
  }
}

然后要去全局注册config\config.default.js:

config.middleware = ['m1', 'm2']; // 顺序很重要,规定中间件经过的顺序

当请求发起的时候,过程如图:

来实现一个接口日志生成功能app\middleware\httpLog.js

const dayjs = require('dayjs'); // 安装个第三方库来解决
const fs = require('fs'); // 用来生成日志文件

module.exports = options => {
  return async (ctx, next) => {
    // console.log(ctx)
    console.log(options) // 获取到config\config.default.js里设置的httpLog.js专用全局变量
    const sTime = Date.now(); // 请求开始时间
    const startTime = dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss"); // 用来放在日志里的请求开始时间
    const req = ctx.request; // 获取到请求内容
    await next();
    const log = { // 日志格式
      method: req.method, // 请求方法
      url: req.url, // 请求地址
      data: req.body, // 请求数据
      startTime, // 开始时间
      endTime: dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss"), // 结束时间
      timeLength: Date.now() - sTime // 请求总时间
    };
    // console.log(log)
    // console.log(ctx.app.baseDir) 可以拿到工程在电脑上的路径地址
    // 下面定义日志内容 时间+标志+日志数据+换行
    const data = dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss") + " [httpLog] " + JSON.stringify(log) + '\r\n';
    fs.appendFileSync(ctx.app.baseDir + '/htttpLog.log', data); // 日志文件生成
  }
}

然后去全局注册
config.middleware = ['httpLog'];

config.httpLog = { // 这个是一些全局变量
  type: 'all'
};

中间件还可以指定接口执行,在路由配置中:

module.exports = app => {
  const { router, controller } = app;
  const userExist = app.middleware.中间件名称();  // 这个中间件的逻辑是如果用户存在就await next();,不存在就直接return,后面controller.user.detail都不执行了,中间件直接返回错误信息给前端
  router.post('/api/user/detail', userExist, controller.user.detail); // 第二个参数直接传入中间件
};

定义公共方法和属性

我们可以在ctx和app上定义一些公共的方法和属性,首先是app,egg规定需要把定义的方法和属性写在app\extend\application.js中:

const path = require('path');

module.exports = {
  // 方法扩展
  package(key){
    const pack = getPack(); // 拿到package.json内容
    return key ? pack[key] : pack; // 返回指定内容
  },

  // 属性扩展
  get allPackage(){
    return getPack(); // 返回package.json内容
  }
};

function getPack(){
  const filePath = path.join(process.cwd(), "package.json"); // 获取到package.json文件的地址
  const pack = require(filePath); // 拿到文件内容
  return pack;
}

在接口定义中使用:

async newApplication(){
  const { ctx, app } = this;
  const packageInfo = app.package('scripts'); // 这样通过自定义公共方法获取到package.json指定内容
  // console.log(packageInfo)
  const allPack = app.allPackage; // 这样通过自定义公共属性获取到package.json指定内容
  console.log(allPack)
  ctx.body = 'newApplication';
}

ctx在app\extend\context.js中定义:

module.exports = {
  params(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;
    }
  }
};

在接口定义中使用:

  async newContext(){
    const { ctx } = this;
    const params = ctx.params('id'); // 通过公共方法拿到指定入参
    console.log(params)
    ctx.body = 'newContext';
  }

ctx下面还有很多属性,这些属性还可以进行自定义方法和属性,例如ctx.request,app\extend\request.js:

module.exports = {
    get token() { // 获取token 为啥有个get关键词,我还不知道
        console.log('header', this.header); // 获取请求头里的东西
        return this.get('token'); // 请求头里的属性用get去获取
    }
};

// 接口中使用
async getToken(){
  const { ctx } = this;
  const token =  ctx.request.token; // 为什么不是执行方法,问就是固定写法

  ctx.body = token;
}

例如ctx.response,app\extend\response.js:

module.exports = {
  set token(token){
    console.log('token', this)
    this.set('token', token);
  }
};

// 接口中使用
async setToken(){
  const { ctx } = this;
  ctx.response.token = 'abc123abc';
  ctx.body = '相应头多个token';
}

例如ctx.helper,\app\extend\helper.js:

module.exports = {
  base64Encode(str = ''){ // 字符串转base64
    return new Buffer(str).toString('base64');
  }
};

// 接口中使用
async setToken(){
  const { ctx } = this;
  ctx.response.token = 'abc123abc';
  const base64Parse = ctx.helper.base64Encode('newResponse');
  ctx.body = base64Parse;
}


自定义插件

除了第三方插件,egg还允许我们写简单的自定义插件,插件一般用来处理业务逻辑,以中间件的形式去写。插件的功能不能影响egg的核心,例如router.js和controller控制器。

例如写个验证用户是否登陆的业务逻辑,首先egg规定插件的创建方式必须遵守

-lib
  -plugin
    -egg-auth // 这里注意一定是egg-开头的
      -app
        --middleware // 插件的原理就是中间件
          auth.js // 这里就写逻辑了 
        --package.json // 说明文件

package.json:

{
  "name": "egg-auth", // 插件名
  "eggPlugin": {
    "name": "auth" // 文件名
  }
}

auth.js:

module.exports = options => {
  console.log('options', options) // 和中间件一样接收全局变量,在config\config.default.js这里配的
  return async (ctx, next) => {
    const url = ctx.request.url; // 获取到请求地址
    console.log('url', url);
    const user = ctx.session.user; // 拿到session设置的用户信息

    if (!user && !options.exclude.includes(ctx.request.url.split('?')[0])) { // 这里限制一些接口不需要用户是否登陆的判断
      ctx.body = {
        status: 1001,
        errMsg: '用户未登录'
      };
    } else {
      await next();
    }
  }
}

写完插件后记得登记一下config\plugin.js:

const path = require('path');
exports.auth = {
  enable: true,
  path: path.join(__dirname, '../lib/plugin/egg-auth')
};

上面需要的全局变量在config\config.default.js设置:

config.auth = { // auth专用变量
  exclude: ['/home', '/user', '/login', '/logout'] // 这个变量用来剔除不需要用户登陆检验的接口
};

然后在全局中启用,app.js:

module.exports = app => {
  app.config.coreMiddleware.push('auth');  // 如果是多个自定义插件,要注意顺序,从下到下经过
  // app.config.coreMiddleware.push('xxx');
  // console.log(app.config.coreMiddleware) 通过打印可以看到经过了哪些插件,且是按照顺序的
}

之后所有接口就生效了(除了上面排除的那四个)。

你可能感兴趣的:(Node.js,javascript,node.js,中间件)