We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Router 描述了请求 URL 与 Controller 的对应关系。Eggjs 约定所有的路由都需要在 app/router.js 中申明,目录结构如下:
┌ app ├── router.js │ ├── controller │ │ ├── home.js │ │ ├── ...
路由和对应的处理方法分开在 2 个地方维护,开发时经常需要在 router.js 与 Controller 之间来回切换。
router.js
Controller
前后台协作时,后端需要为每个 Api 都生成一份对应的 Api 文档给前端。
得益于 JavaScript 加入的 decorator 特性,可以使我们跟 Java/C# 一样,更加直观自然的做面向切面编程:
// 基础版 @route('/intro') async intro() { } // 定义 Method @route('/intro', { method: 'post' }) async intro() { } // 增加权限 @route('/intro', { method: 'post', role: xxxRole }) async intro() { } // Controller 级别中间件 @route('/intro', { method: 'post', role: xxxRole, beforeMiddleware: xxMiddleware }) async intro() { }
为什么设计如此复杂的功能,是不是在滥用 Decorator?
Decorator
先看看 route 的功能:
route
router 官方完整定义中包含的功能:路由定义、中间件、权限,及文档中未直接写的“权限”:
router
router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);
比较下来会发现,只是多了“参数校验”功能。
Eggjs 中参数校验的官方实践:
class PostController extends Controller { async create() { const ctx = this.ctx; try { // 校验参数 // 如果不传第二个参数会自动校验 `ctx.request.body` ctx.validate(createRule); } catch (err) { ctx.logger.warn(err.errors); ctx.body = { success: false }; return; } } };
在我们的业务实践中这个方案会有 2 个问题:
参数漏校验
比如用户提交的数据为 { a: 'a', 'b': 'b', c: 'c' },如果校验规则只定义了 a,那么 b、c 就被漏掉了,并且后续业务中可能会使用这 2 个值。
{ a: 'a', 'b': 'b', c: 'c' }
a
b
c
Eggjs 一个 request 生命周期内,可以随时随地通过 ctx.request 拿到用户数据
ctx.request
因为“参数漏校验”问题的存在,导致后续业务变的不稳定,随时可能会因为用户的异常数据导致业务崩溃,或者出现安全问题。
为了解决“参数漏校验”问题,我们做了如下约定:
Controller 也需要申明入参
class UserController extends Controller { @route('/api/user', { method: 'post' }) async updateUser(username) { // ... } }
上面的例子中,即使用户提交了海量数据,业务代码中也只能拿到 username
username
Controller 之外的业务不应该直接访问 ctx.request 上的数据
也就是说,当某个 Service 方法依赖用户数据时,应该通过入参获取,而不是直接访问 ctx.request
基于以上约定,分别看看 JS、TypeScript 下我们如何解决参数校验问题:
JS
@route('/api/user', { method: 'post', rule: { username: { type: 'string', max: 20 }, } }) async updateUser(username) { // ... }
这里使用了 egg-validate 底层依赖的 parameter 作为校验库
egg-validate
parameter
TypeScript
@route('/api/user', { method: 'post' }) async updateUser(username: R<{ type: string, max: 20 }>) { // ... }
没看错,手动调用 ctx.validate(createRule) 并捕获异常的逻辑确实被我们省略掉了。“懒惰”是提高生产力的第一要素。参数、规则都有了,为什么还要自己撸代码呢?
ctx.validate(createRule)
传统的前后端开发协作方式中,后端提供 Api 给前端调用,代码类似这样:
function updateUser() { request .post(`/api/user`, { username }) .then(ret => { }); }
前端同学需要关注路由、参数、返回值。而这些信息 Controller 都已经有了,直接生成前台 service 用起来是不是更方便呢:
Controller 代码:
export class UserController { @route({ url: '/api/user' }) async getUserInfo(id: number) { return { ... }; } }
生成的 service:
export class UserService extends Base { /** 首页 */ async getUserInfo(id: number) { const __data = { id }; return await this.request({ method: `get`, url: `/api/user`, data: __data, }); } } export const metaService = new UserService(); export default new UserService();
前台使用
import { userService } from 'service/user'; const userInfo = await userService.getUserInfo(id);
对比原来的写法:
function updateUser() { return new Promise((resolve, reject) => { request .post(`/api/user`, { username }) .then(ret => { resolve(ret); }); }); }
userService.getUserInfo 内部封装了 request 逻辑,前端不需要在关心调用过程。
userService.getUserInfo
我们已经把最佳实践抽象为了 egg-controller 插件,可以按下面的步骤安装使用:
安装 egg-controller
egg-controller
tnpm i -S egg-controller
启用插件
打开 config/plugin.js,增加以下配置
aop: { enable: true, package: 'egg-aop', }, controller: { enable: true, package: 'egg-controller', },
使用插件
详细用法参考 egg-controller 文档
The text was updated successfully, but these errors were encountered:
这种前后端约定的方式,应该包装一种 rpc,而不是改变 controller 的概念。
Sorry, something went wrong.
aop在哪里体现了?
thanks for sharing ! 正想学一下 egg
No branches or pull requests
起因
Router 描述了请求 URL 与 Controller 的对应关系。Eggjs 约定所有的路由都需要在 app/router.js 中申明,目录结构如下:
路由和对应的处理方法分开在 2 个地方维护,开发时经常需要在
router.js
与Controller
之间来回切换。前后台协作时,后端需要为每个 Api 都生成一份对应的 Api 文档给前端。
更优雅的实现
得益于 JavaScript 加入的 decorator 特性,可以使我们跟 Java/C# 一样,更加直观自然的做面向切面编程:
为什么是这样的方案
先看看
route
的功能:Controller
级别中间件router
官方完整定义中包含的功能:路由定义、中间件、权限,及文档中未直接写的“权限”:比较下来会发现,只是多了“参数校验”功能。
参数校验
Eggjs 中参数校验的官方实践:
在我们的业务实践中这个方案会有 2 个问题:
参数漏校验
比如用户提交的数据为
{ a: 'a', 'b': 'b', c: 'c' }
,如果校验规则只定义了a
,那么b
、c
就被漏掉了,并且后续业务中可能会使用这 2 个值。Eggjs 一个 request 生命周期内,可以随时随地通过
ctx.request
拿到用户数据因为“参数漏校验”问题的存在,导致后续业务变的不稳定,随时可能会因为用户的异常数据导致业务崩溃,或者出现安全问题。
解决方案
为了解决“参数漏校验”问题,我们做了如下约定:
Controller 也需要申明入参
上面的例子中,即使用户提交了海量数据,业务代码中也只能拿到
username
Controller 之外的业务不应该直接访问
ctx.request
上的数据也就是说,当某个 Service 方法依赖用户数据时,应该通过入参获取,而不是直接访问
ctx.request
基于以上约定,分别看看 JS、TypeScript 下我们如何解决参数校验问题:
JS
这里使用了
egg-validate
底层依赖的parameter
作为校验库TypeScript
没看错,手动调用
ctx.validate(createRule)
并捕获异常的逻辑确实被我们省略掉了。“懒惰”是提高生产力的第一要素。参数、规则都有了,为什么还要自己撸代码呢?新的前后端协作实践
传统的前后端开发协作方式中,后端提供 Api 给前端调用,代码类似这样:
前端同学需要关注路由、参数、返回值。而这些信息 Controller 都已经有了,直接生成前台 service 用起来是不是更方便呢:
Controller 代码:
生成的 service:
前台使用
对比原来的写法:
userService.getUserInfo
内部封装了 request 逻辑,前端不需要在关心调用过程。如何在自己的项目中使用
我们已经把最佳实践抽象为了 egg-controller 插件,可以按下面的步骤安装使用:
安装
egg-controller
启用插件
打开 config/plugin.js,增加以下配置
使用插件
详细用法参考 egg-controller 文档
The text was updated successfully, but these errors were encountered: