egg.js 基础

331 阅读8分钟

基于 node.js 的服务器开发框架 ,‘ Javascript ’,采用promise规范异步函数,基于Koa框架

一、创建 egg.js 项目

先创建一个文件夹,在文件夹中运行如下命令行工具进行脚手架搭建:

  • 脚手架 : npm init egg --type = simple
  • 运行项目 : npm run dev****
  • egg 默认端口为 http://127.0.0.1:7001

mvc 模型model+视图view+控制器controller

  • view 用于展示用户看到的界面
  • controller 用于控制用户的输出和输出,需要用到数据时调用model
  • model 用于操作数据 ————> DB 提供数据

二、HTTP基础

1. query

ctx.query 可以获取get 请求中的查询参数,只会接受第一个,不接受重复值

ctx.querIes 同上,但是可以接受重复值

例子url : cn.bing.com/search?q=你好

在这个url 中, ? 号后面的部分称为 查询参数 ,经常用于 GET 类型的请求中传递参数

2. params

ctx.params ****可以获取get请求中路径上的参数

例子url : /projects/:projectId/app/:appId get 请求可以在路径上声明参数 :

3. body

ctx.request.body可以获取 POST、PUT 和 DELETE 请求体中的数据 (get请求无请求体)

注意:不要把 ctx.request.body ctx.body 混淆,后者其实是 ctx.response.body 的简写。

一般请求中有 body 的时候,客户端(浏览器)会同时发送 Content-Type 告诉服务端这次请求的 body 是什么格式的

请求体body的大小会有限制,可以在config/config.default.js中进行配置

4. cookie

ctx.cookie通过get 来读取 set 来设置

5. 响应

ctx.body设置响应体

ctx.status 设置响应参数

cts.render 渲染模板

ctx.set(key, value) 方法可以设置一个响应头

ctx.set(headers) 设置多个 Heade r

ctx.redirect(url) 重定向

三、controller控制器

作用:

    1. 直接响应数据或渲染模板
    2. 接受用户输入request,解析用户上传的数据,处理后返回相应的结果response( 是业务中唯一和HTTP交互的地方
    3. 与 路由router 建立对应关系

Controller 类 的this 上会有如下属性:

  • this.ctx : 当前请求的上下文 Context 对象的实例,通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。
  • this.app : 当前应用 ****Application ****对象的实例,通过它我们可以拿到框架提供的全局对象和方法。
  • this.service :应用定义的 ****Service ,通过它我们可以访问到抽象出的业务层,等价于 ****this.ctx.service ****
  • this.config :应用运行时的配置项
  • this.logger :logger 对象,上面有四个方法( debug info warn error ),分别代表打印四个不同级别的日志,使用方法和效果与 context logger 中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置

示例:

// 从egg 中导入 controller
const Controller = require('egg').Controller;

// 声明一个类,它继承自 controller 
class HomeController extends Controller {
  const { ctx } = this; 
  // const ctx = this.ctx // 加上这句话可以省略this
  
	// 必须使用异步函数 async 声明一个控制器,这个控制器可以和 router 进行绑定
  async index() {
    
    // 表示响应体为字符串 hello world
    this.ctx.body = 'Hello world';
  }
}

// 导出后其它文件才可以调用
module.exports = HomeController;

释义: ctx 即 centext

  1. this.ctx ****可以获取到当前请求的上下文对象,通过此对象可以便捷的获取请求与响应的属性和方法
  2. this.ctx.body ****响应体内容
  3. this.ctx.request.query (获取get请求) 路径中的查询参数
  4. this.ctx.params (获取get请求) 解析路径
  5. this.ctx.request.body ****获取post请求体的内容
  6. this.ctx.redirect ****重定向
  7. this.ctx.status 响应的状态码

外部重定向: https://www.eggjs.org/zh-CN/basics/router#%E5%A4%96%E9%83%A8%E9%87%8D%E5%AE%9A%E5%90%91

注意:egg默认阻止跨站的post访问,需要进行相应的配置

config.security={
  csrf:{
    enable:false
  }
}

四、router

路由的完整定义分为五个部分: router . 请求动词 ( 别名(可以省略) ``路径 . 中间件(没有可以省略) . 控制器 )

  • 请求动词支持所有HTTP方法 例如: get post put head patch delete 等等
  • router里可以配置多个中间件 middleware
  • controller可以简写 例: app.controller.index.function 简写为字符串形式的 'index.function'
  • 路径支持正则表达式
  • 内部重定向使用 router. redirect ( 访问路径 , 重新向后的路径 ) ****也可以在控制器中做外部重定向

1. 示例

module.exports = (app) => {
  //从app实例中导入 router和controller
  const { router, controller } = app;
  // 处理get请求 '/' 的响应为controller的home文件下的index函数的内容
  router.get('/', controller.home.index);
};

// 可以根据业务逻辑进行拆分,同一个文件可以有多块这样的函数
module.exports = (app) => {
  require('./router/news')(app);
  require('./router/admin')(app);
};

2. 内置RESTful 简写方法

  • router.resources采用了 RESTful 风格预定义了一些URL
module.exports = (app) => {
  const { router, controller } = app;
  router.resources('posts', '/api/posts', controller.posts);
};
MethodPathRoute NameController.Action
GET/postspostsapp.controllers.posts.index
GET/posts/newnew_postapp.controllers.posts.new
GET/posts/:idpostapp.controllers.posts.show
GET/posts/:id/editedit_postapp.controllers.posts.edit
POST/postspostsapp.controllers.posts.create
PUT/posts/:idpostapp.controllers.posts.update
DELETE/posts/:idpostapp.controllers.posts.destroy

3. 适用于多个路由映射

// app/router.js
module.exports = (app) => {
  require('./router/news')(app);
  require('./router/admin')(app);
};

// app/router/news.js
module.exports = (app) => {
  app.router.get('/news/list', app.controller.news.list);
  app.router.get('/news/detail', app.controller.news.detail);
};

// app/router/admin.js
module.exports = (app) => {
  app.router.get('/admin/user', app.controller.admin.user);
  app.router.get('/admin/log', app.controller.admin.log);
};

五、插件

app/config/plugin.js 引入插件 app/config/default.js 配置插件

egg-view-nunjucks nunjucks 模板插件

egg-cors : 跨域请求配置插件

//开启插件
cors: {
    enable: true,
    package: 'egg-cors'
  }

// 配置插件 
config.cors = {
    origin: '*', //允许所有跨域访问
    credentials:true, // 允许跨域请求携带cookies
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
  };

六、中间件

在请求和响应之间执行的一些函数,可以简化逻辑,通常放在app目录下的 middleware 文件夹中

  • 使用方法: router.get('路径', 中间件 ,控制器) 在router文件中直接使用即可
//示例 验证token 可以加在请求数据之前判断用户是否登录
function checkToken() {
    return async function (ctx, next) {
        let token = ctx.request.header.token;
        try {
            let decode = ctx.app.jwt.verify(token, ctx.app.config.jwt.secret)
            if (decode.username && decode.password) {
                await next()
            }else{
                ctx.status=400;
                ctx.body={error:'用户未登录'}
            }
        } catch (error) {
            ctx.status = 400;
            ctx.body = { error: " token 未能通过验证" }
        }

    }
}

module.exports = checkToken;

八、session 和 JWT

  • 判断用户是否登录的两种方法

session

  • egg.js中,设置session非常简单,可以直接在controller 中 用 this .ctx.session设置
class LoginController entends Controller{
    // 登录时下发session
    async login(){
        //if...... 如果登录成功
        this.ctx.session.user=username;	
    }
    // 判断用户是否登录状态
    async get_info(){
        if(this.ctx.session.user){
            // 有session 的操作
        }else{
            // 没有的操作
        }
    }
    // 注销时删除session,设置为空字符串即可
    async logout(){
    	this.ctx.session.user=“ ”;
        // 注销后的其他操作,比如重定向到登录页等
    }
}

JWT

“ json web token ” 即使用token 加密字符串(标识),服务器下发给客户端,客户端每次请求时带上token 证明身份。

  • 安装插件 egg-jwt ****npm install --save egg-jwt
  • 前端收到token后需要保存到localstorage中,并且在以后每次发送请求都要在请求头里带上token
  • 前端使用在请求头里添加token ****{headers: {token: localStorage.getItem("token") } }
  • 服务器使用 ****this.ctx.request.header.token读取前端提交的token
  • 注销功能 直接在前端清空localStorage 中的token即可
// plugin.js
jwt:{
    enable : true,
    package : "egg-jwt"
}

// config.default.js  使用secret加密你的数据,secret不要泄露
config.jwt={
    secret:"加密的字符串"
}
// 服务器端
class JwtController entends Controller{
	async index(){
		// 这是用户数据
		let user = {
			username:'xiaozhang',
			password:'123456'
		}
		// 使用jwt加密
		let token = this.app.jwt.sign(user,this.app.config.jwt.secret);
		// 返回给客户端
		this.ctx.body = token;
		//服务器验证token 解密jwt
		try{
			this.app.jwt.verify(token,this.app.config.jwt.secret)
			this.ctx.body={msg:'response'}
		}catch(error){
			this.ctx.body={error:" token 未能通过验证"}
		}
		
	}
}

// 前端

九、数据持久化(数据库)

orm (对象关系映射),sequelize是一个基于node.js的 orm框架

可以通过egg-sequelize,直接使用sequelize提供的方法操作数据库,无需动手写 SQL

安装:npm install --save egg-sequelize mysql2 mysql2 是数据库类型,根据数据库下载

//相关配置示例

// plugin.js
Sequelize:{
   enable:true,
   package:'egg-sequelize'
}


// config.default.js
config.sequelize={
    dialect:'mysql',
    database:'数据库名',
    host:'localhost',
    port:3306,
    usrname:'root',
    password:'123456'
}

使用 sequelize

github.com/demopark/se… 中文文档

1. 数据类型
  • STRING => varchar(255) 字符串类型
  • INTEGER => int 数值型
  • DOUBLE => deouble 双精度浮点数
  • DATE => datetime 日期
  • TEXT => text 文本
2. model

在app目录下创建一个model文件夹,后续所有关于数据库的数据都保存在这个文件夹中

//创建一张表
module.exports = app =>{
  const{STRING}=app.Sequelize;
  //默认情况下,sequelize将自动将所传递的模型名称转换为复数
  const Clazz = app.model.defing('clazz'{
    // 会自动创建一个自增的 id                           
    name:STRING,                              
      })
}
3. app.js

在根目录下创建一个app.js 文件,这个是全局配置文件

// 将model中的表创建到数据库中
module.exports = app =>{
  // 服务器程序启动时,执行这个函数,beforeStart是node.js生命周期函数
  app.beforeStart(async function(){
    //await app.model.sync({force:true});开发环境使用,会删除数据

    // sync方法会根据模型创建表 即model
    await app.model.sync({});
  })
}
4. 操作数据库都在 控制器和 service中
// 查询数据 select
findAll() 

// 根据条件查询findAll({where:{}}) 
async find(){
  // user.findAll({where:{id:1}}) 从user表中查找 id为1 的所有数据
  await this.app.model.user.findAll({
    where:{id:1;}
  });
}

// 添加数据create({})
async create(){
  // 在user表中加一条数据,列nama  值name
  await this.app.model.user.create( {name:name} )
}


// 修改数据 将user表中,id为1的 数据的name 改为 limin
await this.app.model.user.update(
  {name:'limin'},
  {where:{id:1}}
)

destroy({
  where:{name:'limin,id:1}
}) // 删除数据


// 定义外键
notebook.associate = function(){
  app.model.notebook.belongsTo (app.model.User,{
    foreignKey;'user_id',
    as :'user'
})
}
5. 示例
const { Sequelize, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');

class User extends Model {}
User.init({
  username: DataTypes.STRING,
  birthday: DataTypes.DATE
}, { sequelize, modelName: 'user' });

(async () => {
  await sequelize.sync();
  const jane = await User.create({
    username: 'janedoe',
    birthday: new Date(1980, 6, 20)
  });
  console.log(jane.toJSON());
})();

十、Service

  • Service是Controller 中抽离出来操作数据等作用的逻辑层,可以使Controller保持简洁,可以在多个Controller中重复调用
  • 用户操作ControlllerController通过 Service 操作database
// 定义 创建文件 app/service/user.js
const Service = require('egg').Service;

class UserService extends Service {
  async find(uid) {
    const user = await this.ctx.db.query(
      'select * from user where uid = ?',
      uid,
    );
    return user;
  }
}

module.exports = UserService

注意:一个service 只能包含一个类 需要 module.exports 导出才能访问

十二、 egg.js 配置默认路径

//plugin.js

static: {
    enable: true,
}

//config.default.js
config.static = {
    prefix: '/',
    dir: path.join(appInfo.baseDir, 'app/public'),
    dynamic: true,
    preload: false,
    maxAge: 31536000,
    buffer: true,
  };

config.assets = {
    publicPath: '/public/',
  };

//router
router.get('/', controller.home.index);
//controller

async index() {
    const { ctx } = this
    ctx.response.type = 'html'
    ctx.body = fs.readFileSync(path.resolve(__dirname, '../public/index.html'))
  };