node_egg_controller控制器

// 控制器(Controller)
// 所有的 Controller 文件都必须放在 app/controller 目录下,可以支持多级目录,访问的时候可以通过目录名级联访问

// Router 将用户的请求基于 method 和 URL 分发到了对应的 Controller 上,Controller 负责解析用户的输入,处理后返回相应的结果
  在 RESTful 接口中,Controller 接受用户的参数,从数据库中查找内容返回给用户或者将用户的请求更新到数据库中。
  在 HTML 页面请求中,Controller 根据用户访问不同的 URL,渲染不同的模板得到 HTML 返回给用户。
  在代理服务器中,Controller 将用户的请求转发到其他服务器上,并将其他服务器的处理结果返回给用户。

// 框架推荐 Controller 层主要对用户的请求参数进行处理(校验、转换),然后调用对应的 service 方法处理业务,得到业务结果后封装并返回:
  获取用户通过 HTTP 传递过来的请求参数。
  校验、组装参数。
  调用 Service 进行业务处理,必要时处理转换 Service 的返回结果,让它适应用户的需求。
  通过 HTTP 将结果响应给用户。


// 定义 Controller 类
// app/controller/post.js
const Controller = require('egg').Controller;
class PostController extends Controller {
  async create() {
    const { ctx, service } = this;
    const createRule = {
      title: { type: 'string' },
      content: { type: 'string' },
    };
    // 校验参数
    ctx.validate(createRule);
    // 组装参数
    const author = ctx.session.userId;
    const req = Object.assign(ctx.request.body, { author });
    // 调用 Service 进行业务处理
    const res = await service.post.create(req);
    // 设置响应内容和响应状态码
    ctx.body = { id: res.id };
    ctx.status = 201;
  }
}
module.exports = PostController;

// 我们通过上面的代码定义了一个 PostController 的类,类里面的每一个方法都可以作为一个 Controller 在 Router 中引用到,我们可以从 app.controller 根据文件名和方法名定位到它。
// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.post('createPost', '/api/posts', controller.post.create);
}
// Controller 支持多级目录,例如如果我们将上面的 Controller 代码放到 app/controller/sub/post.js 中
// app/router.js
module.exports = app => {
  app.router.post('createPost', '/api/posts', app.controller.sub.post.create);
}


// 定义的 Controller 类,会在每一个请求访问到 server 时实例化一个全新的对象,而项目中的 Controller 类继承于 egg.Controller,会有下面几个属性挂在 this 上。
  this.ctx:       当前请求的上下文 Context 对象的实例,通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。
  this.app:       当前应用 Application 对象的实例,通过它我们可以拿到框架提供的全局对象和方法。
  this.service:  应用定义的 Service,通过它我们可以访问到抽象出的业务层,等价于 this.ctx.service 。
  this.config:   应用运行时的配置项。
  this.logger:   logger 对象,上面有四个方法(debug,info,warn,error),分别代表打印四个不同级别的日志,

// HTTP 基础, 由于 Controller 基本上是业务开发中唯一和 HTTP 协议打交道的地方

// 通过 curl 发出的 HTTP 请求的内容就会是下面这样的

  //请求行
  // method:这个请求中 method 的值是 POST。
  // path:值为 /api/posts,如果用户的请求中包含 query,也会在这里出现
  POST /api/posts HTTP/1.1

  // 到空行的位置都是请求头
  // 浏览器发起请求的时候,域名会用来通过 DNS 解析找到服务的 IP 地址,但是浏览器也会将域名和端口号放在 Host 头中一并发送给服务端。
  Host: localhost:3000 
  // 当我们的请求有 body 的时候,都会有 Content-Type 来标明我们的请求体是什么格式的。
  Content-Type: application/json; charset=UTF-8
  // 空行

  // 请求的 body,当请求是 POST, PUT, DELETE 等方法的时候,可以带上请求体,服务端会根据 Content-Type 来解析请求体。
  {"title": "controller", "content": "what is controller"}



// 在服务端处理完这个请求后,会发送一个 HTTP 响应给客户端
  // 响应行
  // HTTP协议, 状态码201
  HTTP/1.1 201 Created
  // 响应头
  Content-Type: application/json; charset=utf-8 //响应的格式是 JSON
  Content-Length: 8 // 长度为 8 个字节。
  Date: Mon, 09 Jan 2017 08:40:28 GMT // 时间
  Connection: keep-alive
  // 空行

  // 请求响应返回的数据
  {"id": 1}



// 获取 HTTP 请求参数
// 从上面的 HTTP 请求示例中可以看到,有好多地方可以放用户的请求数据,
// 框架通过在 Controller 上绑定的 Context 实例,提供了许多便捷方法和属性获取用户通过 HTTP 请求发送过来的参数。

// query
// URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 类型的请求中传递参数,
// category=egg&language=node,我们可以通过 ctx.query 拿到解析过后的这个参数体

// 当 Query String 中的 key 重复时,ctx.query 只取 key 第一次出现时的值,后面再出现的都会被忽略。
// category=egg&category=koa 通过 ctx.query 拿到的值是 { category: 'egg' }

// 获取重复的参数 GET /posts?category=egg&id=1&id=2&id=3, 框架提供了 ctx.queries 对象

// 在 Router 中,我们介绍了 Router 上也可以申明参数,这些参数都可以通过 ctx.params 获取到。
// app.get('/projects/:projectId/app/:appId', 'controller.filename.listPosts');
class PostController extends Controller {
  async listPosts() {
    const query = this.ctx.query; // => { category: 'egg', language: 'node'}
    const queries = this.ctx.queries; // => { category : 'egg', id: ['1', '2', '3']}
    const params = this.ctx.params.projectId || this.ctx.params.appId;  
  }
}


// body 
// 虽然我们可以通过 URL 传递参数,但是还是有诸多限制:
  // 浏览器中会对 URL 的长度有所限制,如果需要传递的参数过多就会无法传递。
  
  // 服务端经常会将访问的完整 URL 记录到日志文件中,有一些敏感数据通过 URL 传递会不安全。
  
  // 在前面的 HTTP 请求报文示例中,我们看到在 header 之后还有一个 body 部分,我们通常会在这个部分传递 POST、PUT 和 DELETE 等方法的参数。
  // 一般请求中有 body 的时候,客户端(浏览器)会同时发送 Content-Type 告诉服务端这次请求的 body 是什么格式的。Web 开发中数据传递最常用的两类格式分别是 JSON 和 Form。
  
  // 框架内置了 bodyParser 中间件来对这两类格式的请求 body 解析成 object 挂载到 ctx.request.body 上。
  // HTTP 协议中并不建议在通过 GET、HEAD 方法访问时传递 body,所以我们无法在 GET、HEAD 方法中按照此方法获取到内容。

  
  // POST /api/posts HTTP/1.1
  // Host: localhost:3000
  // Content-Type: application/json; charset=UTF-8
  //
  // {"title": "controller", "content": "what is controller"}
  class PostController extends Controller {
    async listPosts() {
      const title = this.ctx.request.body.title; // => controller
      const content = this.ctx.request.body.content // => 'what is controller'
    }
  }
// 框架对 bodyParser 设置了一些默认参数,配置好之后拥有以下特性:

//   当请求的 Content-Type 为 application/json,application/json-patch+json,application/vnd.api+json 和 application/csp-report 时,会按照 json 格式对请求 body 进行解析,并限制 body 最大长度为 100kb。
  
//   当请求的 Content-Type 为 application/x-www-form-urlencoded 时,会按照 form 格式对请求 body 进行解析,并限制 body 最大长度为 100kb。
  
//   如果解析成功,body 一定会是一个 Object(可能是一个数组)。
  
//   一般来说我们最经常调整的配置项就是变更解析时允许的最大长度,可以在 config/config.default.js 中覆盖框架的默认值。
  module.exports = {
    bodyParser: {
      jsonLimit: '1mb',
      formLimit: '1mb',
    },
  };


// header 除了从 URL 和请求 body 上获取参数之外,还有许多参数是通过请求 header 传递的
  ctx.headers,ctx.header,ctx.request.headers,ctx.request.header:这几个方法是等价的,都是获取整个 header 对象。
  ctx.get(name),ctx.request.get(name):获取请求 header 中的一个字段的值,如果这个字段不存在,会返回空字符串。
  我们建议用 ctx.get(name) 而不是 ctx.headers['name'],因为前者会自动处理大小写。

// Cookie,HTTP 请求都是无状态的,Web 应用通常都需要知道发起请求的人是谁。为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie
// 通过 ctx.cookies,我们可以在 Controller 中便捷、安全的设置和读取 Cookie。
class CookieController extends Controller {
  async add() {
    const ctx = this.ctx;
    let count = ctx.cookies.get('count');
    count = count ? Number(count) : 0;
    ctx.cookies.set('count', ++count);
    ctx.body = count;
  }
  async remove() {
    const ctx = this.ctx;
    const count = ctx.cookies.set('count', null);
    ctx.status = 204;
  }
}
// 对于 Cookie 来说,主要有下面几个属性可以在 config.default.js 中进行配置:
  module.exports = {
    cookies: {
      // httpOnly: true | false,
      // sameSite: 'none|lax|strict',
    },
  };


// Session,通过 Cookie,我们可以给每一个用户设置一个 Session,用来存储用户身份相关的信息,这份信息会加密后存储在 Cookie 中,实现跨请求的用户身份保持。
// 框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session 。

  class PostController extends Controller {
    async fetchPosts() {
      const ctx = this.ctx;
      // 获取 Session 上的内容
      const userId = ctx.session.userId;
      const posts = await ctx.service.post.fetch(userId);
      // 修改 Session 的值
      ctx.session.visited = ctx.session.visited ? ++ctx.session.visited : 1;
      ctx.body = {
        success: true,
        posts,
      };
    }
    // 如果要删除它,直接将它赋值为 null
    async deleteSession() {
      this.ctx.session = null;
    }
  }
// 对于 Session 来说,主要有下面几个属性可以在 config.default.js 中进行配置:
  module.exports = {
    key: 'EGG_SESS', // 承载 Session 的 Cookie 键值对名字
    maxAge: 86400000, // Session 的最大有效时间
  };


// 参数校验 npm install egg-validate --save
// 借助 Validate 插件提供便捷的参数校验机制,帮助我们完成各种复杂的参数校验。
// config/plugin.js
exports.validate = {
  enable: true,
  package: 'egg-validate',
};

// 当校验异常时,会直接抛出一个异常,异常的状态码为 422,errors 字段包含了详细的验证不通过信息。如果想要自己处理检查的异常,可以通过 try catch 来自行捕获。
class PostController extends Controller {
  async create() {
    const createRule = {
      title: { type: 'string' },
      content: { type: 'string' },
    }
    const ctx = this.ctx;
    try {
      ctx.validate(createRule);
    } catch (err) {
      ctx.logger.warn(err.errors);
      ctx.body = { success: false };
      return;
    }
  }
};

// 调用 Service
// 我们并不想在 Controller 中实现太多业务逻辑,所以提供了一个 Service 层进行业务逻辑的封装,这不仅能提高代码的复用性,同时可以让我们的业务逻辑更好测试。
// 在 Controller 中可以调用任何一个 Service 上的任何方法,同时 Service 是懒加载的,只有当访问到它的时候框架才会去实例化它。

class PostController extends Controller {
  async create() {
    const ctx = this.ctx;
    const author = ctx.session.userId;
    const req = Object.assign(ctx.request.body, { author });
    // 调用 service 进行业务处理
    const res = await ctx.service.post.create(req);
    ctx.body = { id: res.id };
    ctx.status = 201;
  }
}


// 发送 HTTP 响应
// 当业务逻辑完成之后,Controller 的最后一个职责就是将业务逻辑的处理结果通过 HTTP 响应发送给用户。
// 设置 status 框架提供了一个便捷的 Setter 来进行状态码的设置
class PostController extends Controller {
  async create() {
    // 设置状态码为 201
    this.ctx.status = 201;
  }
};
// 设置 body, ctx.body 是 ctx.response.body 的简写,不要和 ctx.request.body 混淆了。
// class ViewController extends Controller {
  async show() {
    this.ctx.body = {
      name: 'egg',
      category: 'framework',
      language: 'Node.js',
    };
  }
  async page() {
    this.ctx.body = '<html><h1>Hello</h1></html>';
  }
}

// 渲染模板, 可以直接使用 ctx.render(template) 来渲染模板生成 html
class HomeController extends Controller {
  async index() {
    const ctx = this.ctx;
    await ctx.render('home.tpl', { name: 'egg' });
    // ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });
  }
};

猜你喜欢

转载自www.cnblogs.com/JunLan/p/12536278.html
今日推荐