uni-cloud-router.md 8.7 KB
Newer Older
inkwalk's avatar
inkwalk 已提交
1 2 3 4
# uni-cloud-router

> 基于 koa 风格的 uniCloud 云函数路由库,同时支持 uniCloud 客户端及 URL 化访问

fxy060608's avatar
fxy060608 已提交
5 6
源码仓库:[https://gitee.com/dcloud/uni-cloud-router](https://gitee.com/dcloud/uni-cloud-router)

inkwalk's avatar
inkwalk 已提交
7
## 介绍
inkwalk's avatar
inkwalk 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20

### 目录结构

```bash
├── package.json
├── index.js // 云函数入口文件
├── config.js // 用于配置 router 应用根目录、中间件等
├── controller // 用于解析用户的输入,处理后返回相应的结果
|   ├── user.js
├── service (可选) //用于编写业务逻辑层,建议使用
|   ├── user.js
```

inkwalk's avatar
inkwalk 已提交
21 22 23 24 25 26
### 快速开始

为了快速上手,提供了一个简单的 demo 示例,以创建是一个 `hello-uni-cloud-router` 云函数为例,演示如何通过 `uni-cloud-router` 组织代码:

**1.添加入口文件**

inkwalk's avatar
inkwalk 已提交
27
```js
inkwalk's avatar
inkwalk 已提交
28 29 30
// index.js (通常无需改动)做
const Router = require("uni-cloud-router").Router; // 引入 Router
const router = new Router(require("./config.js")); // 根据 config 初始化 Router
inkwalk's avatar
inkwalk 已提交
31
exports.main = async (event, context) => {
inkwalk's avatar
inkwalk 已提交
32 33
  return router.serve(event, context); // 由 Router 接管云函数
};
inkwalk's avatar
inkwalk 已提交
34 35
```

inkwalk's avatar
inkwalk 已提交
36 37
**2.添加配置文件**

inkwalk's avatar
inkwalk 已提交
38 39 40 41 42 43
```js
// config.js
module.exports = {
  debug: true, // 调试模式时,将返回 stack 错误堆栈
  baseDir: __dirname, // 必选,应用根目录
  middleware: [], // 自定义中间件
inkwalk's avatar
inkwalk 已提交
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
};
```

**3.在 controller 文件夹下创建一个 hello.js**

创建一个 controller

```js
const { Controller } = require("uni-cloud-router");
module.exports = class HelloController extends (
  Controller
) {
  sayHello() {
    return this.service.hello.sayHello();
  }
};
inkwalk's avatar
inkwalk 已提交
60 61
```

inkwalk's avatar
inkwalk 已提交
62 63 64 65
**4.在 service 文件夹下创建一个 hello.js**

创建一个 service

inkwalk's avatar
inkwalk 已提交
66
```js
inkwalk's avatar
inkwalk 已提交
67 68 69 70 71 72 73 74
const { Service } = require("uni-cloud-router");
module.exports = class HelloService extends (
  Service
) {
  sayHello() {
    return {
      data: "welcome to uni-cloud-router!",
    };
inkwalk's avatar
inkwalk 已提交
75
  }
inkwalk's avatar
inkwalk 已提交
76 77 78 79 80 81 82 83 84 85 86 87 88 89
};
```

到这里,已创建好了是一个 `hello-uni-cloud-router` 云函数(注意:需上传云函数后,前端才能访问)。

**5.在页面里调用云函数**

在页面中 URL 化访问 hello(controller)下 sayHello:

```js
sayHello() {
  this.request('hello/sayHello', {}).then(res => {
    this.title = res.data
  })
inkwalk's avatar
inkwalk 已提交
90 91 92
}
```

inkwalk's avatar
inkwalk 已提交
93 94 95 96
以上代码仅作为示例,建议点击右侧【使用 HBuilderX 导入示例项目】尝试。

## 深入学习

inkwalk's avatar
inkwalk 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
### 控制器(Controller)

负责解析用户的输入,处理后返回相应的结果。

推荐 Controller 层主要对用户的请求参数进行处理(校验、转换),然后调用对应的 service 方法处理业务,得到业务结果后封装并返回:

1. 获取用户传递过来的请求参数。
2. 校验、组装参数。
3. 调用 Service 进行业务处理,必要时处理转换 Service 的返回结果,让它适应用户的需求。
4. 将结果响应给用户。

#### 如何编写 Controller

所有的 Controller 文件都必须放在 `controller` 目录下,可以支持多级目录,访问的时候可以通过目录名级联访问。

```js
// controller/post.js
inkwalk's avatar
inkwalk 已提交
114
const Controller = require("uni-cloud-router").Controller;
inkwalk's avatar
inkwalk 已提交
115
// 必须继承 Controller 类
inkwalk's avatar
inkwalk 已提交
116 117 118
module.exports = class PostController extends (
  Controller
) {
inkwalk's avatar
inkwalk 已提交
119
  async create() {
inkwalk's avatar
inkwalk 已提交
120
    const { ctx, service } = this;
inkwalk's avatar
inkwalk 已提交
121 122
    // 校验参数
    ctx.validate({
inkwalk's avatar
inkwalk 已提交
123 124 125
      title: { type: "string" },
      content: { type: "string" },
    });
inkwalk's avatar
inkwalk 已提交
126
    // 组装参数
inkwalk's avatar
inkwalk 已提交
127 128
    const author = ctx.auth.uid;
    const post = Object.assign(ctx.data, { author });
inkwalk's avatar
inkwalk 已提交
129
    // 调用 Service 进行业务处理
inkwalk's avatar
inkwalk 已提交
130
    return service.post.create(post);
inkwalk's avatar
inkwalk 已提交
131
  }
inkwalk's avatar
inkwalk 已提交
132
};
inkwalk's avatar
inkwalk 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
```

定义的 Controller 类,会在每一个请求访问时实例化一个全新的对象,会有下面几个属性挂在 `this` 上。

- `this.ctx`:当前请求的上下文对象的实例,通过它我们可以拿到各种便捷属性和方法。
- `this.service`:应用定义的 service,通过它我们可以访问到抽象出的业务层,等同于 `this.ctx.service`
- `this.db`:等同于 `uniCloud.database()`
- `this.curl`:等同于 `uniCloud.httpclient.request`
- `this.throw`:抛出异常信息,等同于 `this.ctx.throw`

#### 获取请求参数

通过在 Controller 上绑定的 Context 实例的 data 属性,获取请求发送过来的参数

```js
class PostController extends Controller {
  async listPosts() {
inkwalk's avatar
inkwalk 已提交
150
    const data = this.ctx.data;
inkwalk's avatar
inkwalk 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    // {
    //   username: 'demo',
    //   password: 'demo',
    // }
  }
}
```

#### 调用 Service

通过 Service 层进行业务逻辑的封装,不仅能提高代码的复用性,同时可以让业务逻辑更好测试。

Controller 中可以调用任何一个 Service 上的任何方法,同时 Service 是懒加载的,只有当访问到它的时候才会去实例化它。

```js
class PostController extends Controller {
  async create() {
inkwalk's avatar
inkwalk 已提交
168 169 170
    const { ctx, service } = this;
    const author = ctx.auth.uid;
    const post = Object.assign(ctx.data, { author });
inkwalk's avatar
inkwalk 已提交
171
    // 调用 service 进行业务处理
inkwalk's avatar
inkwalk 已提交
172
    return service.post.create(post);
inkwalk's avatar
inkwalk 已提交
173 174 175 176 177 178 179 180 181 182 183 184
  }
}
```

Service 的具体写法,请查看 [Service](#服务service) 章节。

#### 定制 URL 化返回的状态码

```js
class PostController extends Controller {
  async create() {
    // 设置状态码为 201
inkwalk's avatar
inkwalk 已提交
185
    this.ctx.status = 201; // 仅当使用 HTTP/HTTPS 请求时生效
inkwalk's avatar
inkwalk 已提交
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
  }
}
```

### 服务(Service)

业务逻辑封装的一个抽象层,有以下几个好处:

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

#### 使用场景

- 复杂数据的处理,比如要展现的信息需要从数据库获取,还要经过一定的规则计算,才能返回用户显示。或者计算完成后,更新到数据库。
- 第三方服务的调用,比如 微信模板消息推送 等。

#### 如何编写 Service

所有的 Service 文件都必须放在 `service` 目录下,可以支持多级目录,访问的时候可以通过目录名级联访问。

```js
// service/post.js
inkwalk's avatar
inkwalk 已提交
209
const Service = require("uni-cloud-router").Service;
inkwalk's avatar
inkwalk 已提交
210
// 必须继承 Service
inkwalk's avatar
inkwalk 已提交
211 212 213
module.exports = class PostService extends (
  Service
) {
inkwalk's avatar
inkwalk 已提交
214
  async create(data) {
inkwalk's avatar
inkwalk 已提交
215
    return this.db.add(data);
inkwalk's avatar
inkwalk 已提交
216
  }
inkwalk's avatar
inkwalk 已提交
217
};
inkwalk's avatar
inkwalk 已提交
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
```

定义的 Service 类是懒加载的,只有当访问到它的时候才会去实例化它,会有下面几个属性挂在 `this` 上。

- `this.ctx`:当前请求的上下文对象的实例,通过它我们可以拿到各种便捷属性和方法。
- `this.service`:应用定义的 service,通过它我们可以访问到抽象出的业务层,等同于 `this.ctx.service`
- `this.db`:等同于 `uniCloud.database()`
- `this.curl`:等同于 `uniCloud.httpclient.request`
- `this.throw`:抛出异常信息,等同于 `this.ctx.throw`

#### 使用 Service

[在 Controller 中调用 Service](#调用-service)

### 中间件(Middleware)

在路由请求前,后添加处理逻辑,实现一些特定功能,如:用户登录,权限校验等

#### 开发中间件

与 koa 保持一致,参考:[koa 中间件](https://demopark.github.io/koa-docs-Zh-CN/guide.html)

```js
// middleware/auth.js
inkwalk's avatar
inkwalk 已提交
242
const uniID = require("uni-id");
inkwalk's avatar
inkwalk 已提交
243 244
module.exports = (options) => {
  // 初始化 uniID 配置
inkwalk's avatar
inkwalk 已提交
245
  uniID.init(options);
inkwalk's avatar
inkwalk 已提交
246 247 248
  // 返回中间件函数
  return async function auth(ctx, next) {
    // 校验 token
inkwalk's avatar
inkwalk 已提交
249
    const auth = uniID.checkToken(ctx.event.uniIdToken);
inkwalk's avatar
inkwalk 已提交
250 251
    if (auth.code) {
      // 校验失败,抛出错误信息
inkwalk's avatar
inkwalk 已提交
252
      throw { code: auth.code, message: auth.message };
inkwalk's avatar
inkwalk 已提交
253
    }
inkwalk's avatar
inkwalk 已提交
254 255 256 257
    ctx.auth = auth; // 设置当前请求的 auth 对象
    await next(); // 执行后续中间件
  };
};
inkwalk's avatar
inkwalk 已提交
258 259 260 261 262 263 264 265 266 267 268 269 270
```

示例:

- [uni-id 校验 token 中间件](https://github.com/dcloudio/uni-template-admin/blob/master/cloudfunctions-aliyun/uni-admin/middleware/auth.js)
- [uni-id 校验 permission 中间件](https://github.com/dcloudio/uni-template-admin/blob/master/cloudfunctions-aliyun/uni-admin/middleware/permission.js)
- [云函数 URL 化中间件](https://github.com/fxy060608/uni-cloud-router/blob/master/src/middleware/http.ts)

#### 使用中间件

1. 通过 config.js 配置

```js
inkwalk's avatar
inkwalk 已提交
271
const auth = require("./middleware/auth.js"); // 引入 auth 中间件
inkwalk's avatar
inkwalk 已提交
272 273 274 275 276 277
module.exports = {
  debug: true, // 调试模式时,将返回 stack 错误堆栈
  baseDir: __dirname, // 指定应用根目录
  middleware: [
    [
      //数组格式,第一个元素为中间件,第二个元素为中间件生效规则配置
inkwalk's avatar
inkwalk 已提交
278
      auth({ tokenSecret: "tokenSecret-demo" }), // 注册中间件
inkwalk's avatar
inkwalk 已提交
279 280 281
      { enable: true, ignore: /\/login$/ }, // 配置当前中间件生效规则,该规则表示以`/login`结尾的路由不会执行 auth 中间件校验 token
    ],
  ],
inkwalk's avatar
inkwalk 已提交
282
};
inkwalk's avatar
inkwalk 已提交
283 284 285 286 287
```

2. 中间件配置项

- enable 控制中间件是否开启。
inkwalk's avatar
inkwalk 已提交
288
- match 设置