本资源由 itjc8.com 收集整理

今天我想和你聊一聊怎么基于 Serverless 构建弹性可扩展的 Restful API。

API 是使用 Serverless 最常见,也是最适合的场景之一。和 Serverful 架构的 API 相比,用 Serverless 开发 API 好处很多:

  • 不用购买、管理服务器等基础设施,不用关心服务器的运维,节省人力成本;

  • 基于 Serverless 的 API,具备自动弹性伸缩的能力,能根据请求流量弹性扩缩容,让你不再担心流量波峰、波谷;

  • 基于 Serverless 的 API 按实际资源使用量来付费,节省财务成本。

因为好处很多,很多开发者跃跃欲试,但在实践过程中却遇到了很多问题,比如怎么设计最优的架构?怎么组织代码?怎么管理多个函数?所以今天我就以开发一个内容管理系统为例,带你学习怎么基于 Serverless 去开发一个 Restful API,解决上述共性问题。

首先,我们需要对内容管理系统进行架构设计。

内容管理系统的架构设计

在进行架构设计前,你要明确系统的需求。对于一个内容管理系统,最核心的功能(也是这一讲要实现的功能),主要有这样几个:

  • 用户注册;

  • 用户登录;

  • 发布文章;

  • 修改文章;

  • 删除文章;

  • 查询文章。

这 6 个功能分别对应了我们要实现的 Restful API。为了方便统一管理 API,在 Serverless 架构中我们通常会用到 API 网关,通过 API 网关触发函数执行,并且基于 API 网关我们还可以实现参数控制、超时时间、IP 黑名单、流量控制等高级功能。

对于文章管理相关的 Restful API,用户发布文章前需要先登录,在 15 讲,你已经知道在 Serverless 中可以用 JWT 进行身份认证,咱们的管理系统中的登录注册功能也将沿用上一讲的内容。

在传统的 Serverful 架构中,通常会用 MySQL 等关系型数据库存储数据,但因为关系型数据库要在代码中维护连接状态及连接池,且一般不能自动扩容,并不适合 Serverless 应用,所以在 Serverless 架构中,通常选用表格存储等 Serverless NoSQL 数据来存储数据。

基于 JWT 的身份认证方案、数据存储方案,我们可以画出 Serverless 的内容管理系统架构图:

16  API:基于 Servele 构建弹性可扩展的 Retful API - 图1

图中主要表达的意思是: 通过 API 网关承接用户请求,并驱动函数执行。每个函数分别实现一个具体功能,并通过 JWT 实现身份认证,最后表格存储作为数据库。

其中,数据库中存储的数据主要是用户数据和文章数据。假设用户有 username(用户名) 和 password(密码) 两个属性;文章有 article_id(文章 ID)、username(创建者)、title(文章标题)、content(文章内容)、create_date(创建时间)、update_date(更新时间)这几个属性。

16  API:基于 Servele 构建弹性可扩展的 Retful API - 图2

接下来,你可以在表格存储中创建对应的数据表(你可以在表格存储控制台创建,也可以直接用我提供的这段代码进行创建):

// index.jsconstrequire”tablestore”// 初始化 TableStore clientconstnewaccessKeyId’‘accessKeySecret’‘endpoint”https://serverless-app.cn-shanghai.ots.aliyuncs.com"instancename"serverless-cms"/**

  • 创建 user 表 *
  • 参考文档: https://help.aliyun.com/document_detail/100594.html /asyncfunction createUserTable() consttableMetatableName”user”primaryKeyname”username”// 用户名typedefinedColumnname”password”// 密码type// 为数据表配置预留读吞吐量或预留写吞吐量。0 表示不预留吞吐量,完全按量付费reservedThroughputcapacityUnitread0write0tableOptions// 数据的过期时间,单位为秒,-1表示永不过期timeToLive-1// 保存的最大版本数,1 表示每列上最多保存一个版本即保存最新的版本maxVersions1await/*
  • 创建文章表 */asyncfunction createArticleTable() consttableMetatableName”article”primaryKeyname”article_id”// 文章 ID,唯一字符串typedefinedColumnname”title”typename”username”typename”content”typename”create_date”typename”update_date”type// 为数据表配置预留读吞吐量或预留写吞吐量。0 表示不预留吞吐量,完全按量付费reservedThroughputcapacityUnitread0write0tableOptions// 数据的过期时间,单位为秒,-1表示永不过期timeToLive-1// 保存的最大版本数,1 表示每列上最多保存一个版本即保存最新的版本maxVersions1awaitasyncfunction () awaitawait```

    TableStore = ();

    client = TableStore.Client({ : , : , : , : , });

    { table = { : { : , : [

    1. {
    2. : ,
    3. : TableStore.PrimaryKeyType.STRING,
    4. },

    ], : [

    1. {
    2. : ,
    3. : TableStore.DefinedColumnType.DCT_STRING,
    4. },

    ], },

    : { : {

    1. : ,
    2. : ,

    }, }, : {

    : ,

    : , }, }; client.createTable(table); }

    { table = { : { : , : [

    1. {
    2. : ,
    3. : TableStore.PrimaryKeyType.STRING,
    4. },

    ], : [

    1. {
    2. : ,
    3. : TableStore.DefinedColumnType.DCT_STRING,
    4. },
    5. {
    6. : ,
    7. : TableStore.DefinedColumnType.DCT_STRING,
    8. },
    9. {
    10. : ,
    11. : TableStore.DefinedColumnType.DCT_STRING,
    12. },
    13. {
    14. : ,
    15. : TableStore.DefinedColumnType.DCT_STRING,
    16. },
    17. {
    18. : ,
    19. : TableStore.DefinedColumnType.DCT_STRING,
    20. },

    ], },

    : { : {

    1. : ,
    2. : ,

    }, }, : {

    : ,

    : , }, }; client.createTable(table); } ( { createUserTable(); createArticleTable(); })(); ```

这段代码主要创建了 user 和 article 两张表,其中 user 表的主键是 username,article 表的主键是 article_id,主键的作用是方便查询。除了主键,我还定义了几个列。其实对于表格存储,默认也可以不创建列,表格存储是宽表,除主键外,数据列可以随意扩展。

在完成了数据库表的创建后,我们就可以开始进行系统实现了。

内容管理系统的实现

为了方便你学习,我为你提供了完整代码(代码地址),你可以参考着学习。

//github.com/nodejh/serverless-class15``` $ git clone https: $ cd /cms

  1. 整个代码目录结构如下:
  2. package

. ├── .json ├── src │ ├── config │ │ └── index.js │ ├── db │ │ └── client.js │ ├── function │ │ ├── article │ │ │ ├── create.js │ │ │ ├── delete.js │ │ │ ├── detail.js │ │ │ └── update.js │ │ └── user │ │ ├── login.js │ │ └── register.js │ └── middleware │ └── auth.js └── template.yml

  1. 其中,所有业务代码都放在 src 目录中:
  2. -
  3. config/index.js 是配置文件,里面包含身份凭证等配置信息;
  4. -
  5. db/client.js 对表格存储的增删改查操作进行了封装,方便在函数中使用(将数据库的操作封装还有一个好处是,如果你之后想要迁移到其他数据库,只要修改 db/client.js 中的逻辑,不用修改业务代码);
  6. -
  7. middleware 目录中是一些中间件,比如 auth.js,用于身份认证;
  8. -
  9. functions 目录中就是所有函数,登录、注册、创建文章等,每个功能分别对应一个函数;
  10. -
  11. template.yaml 是应用配置文件,包括函数和 API 网关的配置。
  12. 根据前面梳理的系统功能,我们需要实现以下几个 API
  13. | 用户注册 | POST /user/register |
  14. | --- | --- |
  15. | 用户登录 | POST /user/login |
  16. | 发布文章 | POST /article/create |
  17. | 查询文章 | GET /article/detail/[article_id] |
  18. | 更新文章 | POST /article/update |
  19. | 删除文章 | PUT /article/delete/[article_id] |
  20. 每个 API 对应一个具体的函数,每个函数也都有一个与之对应的 API 网关触发器。由于这些函数属于同一个应用,所以我们可以通过一个 template.yaml 来定义所有函数。同时也可以在 template.yaml 中定义函数的 API 网关触发器,这样部署函数时,就会自动创建 API 网关。
  21. 内容管理系统的 template.yaml 格式如下:
  22. ROSTemplateFormatVersion:'2015-09-01'Transform:'Aliyun::Serverless-2018-04-03'Resources:# 函数服务,该服务中的函数都是内容管理系统的函数serverless-cms:Type:'Aliyun::Serverless::Service'Properties:Description:'Serverless 内容管理系统'# 函数名称[functionName]:Type:'Aliyun::Serverless::Function'Properties:# 函数路径Handler:<functionPath>.handlerRuntime:nodejs12CodeUri:'./'# API 网关分组,分钟中的所有 API 都是内容管理系统的 APIServerlessCMSGroup:Type:'Aliyun::Serverless::Api'Properties:StageName:RELEASEDefinitionBody:<Path>:# 请求的 pathpost:# 请求的 methodx-aliyun-apigateway-api-name:user_register# API 名称x-aliyun-apigateway-fc:# 当请求该 API 时,要触发的函数,arn:acs:fc:::services/${serverless-cms.Arn}/functions/${<functionName>.Arn}/timeout:3000
  23. **template.yaml 主要分为两部分:** 函数定义和 API 网关定义,每个函数都有一个与之对应的 API 网关。我们用 serverless-cms 服务来表示内容管理系统这个应用,服务内的所有函数都是内容管理系统的函数。同理,ServerlessCMSGroup 这个 API 网关分组中的所有 API 都是内容管理系统的 API
  24. 完整的 template.yaml 配置如下:
  25. ROSTemplateFormatVersion:'2015-09-01'Transform:'Aliyun::Serverless-2018-04-03'Resources:# 函数服务serverless-cms:Type:'Aliyun::Serverless::Service'Properties:Description:'Serverless 内容管理系统'user-register:Type:'Aliyun::Serverless::Function'Properties:Handler:src/function/user/register.handlerRuntime:nodejs12CodeUri:'./'user-login:Type:'Aliyun::Serverless::Function'Properties:Handler:src/function/user/login.handlerRuntime:nodejs12CodeUri:'./'article-create:Type:'Aliyun::Serverless::Function'Properties:Handler:src/function/article/create.handlerRuntime:nodejs12CodeUri:'./'article-detail:Type:'Aliyun::Serverless::Function'Properties:Handler:src/function/article/detail.handlerRuntime:nodejs12CodeUri:'./'article-update:Type:'Aliyun::Serverless::Function'Properties:Handler:src/function/article/update.handlerRuntime:nodejs12CodeUri:'./'article-delete:Type:'Aliyun::Serverless::Function'Properties:Handler:src/function/article/delete.handlerRuntime:nodejs12CodeUri:'./'# API 网关分组ServerlessCMSGroup:Type:'Aliyun::Serverless::Api'Properties:StageName:RELEASEDefinitionBody:'/user/register':# 请求的 pathpost:# 请求的 methodx-aliyun-apigateway-api-name:user_register# API 名称x-aliyun-apigateway-fc:# 当请求该 API 时,要触发的函数,arn:acs:fc:::services/${serverless-cms.Arn}/functions/${user-register.Arn}/timeout:3000'/user/login':post:x-aliyun-apigateway-api-name:user_loginx-aliyun-apigateway-fc:arn:acs:fc:::services/${serverless-cms.Arn}/functions/${user-login.Arn}/timeout:3000'/article/create':post:x-aliyun-apigateway-api-name:article_createx-aliyun-apigateway-fc:arn:acs:fc:::services/${serverless-cms.Arn}/functions/${article-create.Arn}/timeout:3000'/article/detail/[article_id]':GET:x-aliyun-apigateway-api-name:article_detailx-aliyun-apigateway-request-parameters:-apiParameterName:'article_id'location:'Path'parameterType:'String'required:'REQUIRED'x-aliyun-apigateway-fc:arn:acs:fc:::services/${serverless-cms.Arn}/functions/${article-detail.Arn}/timeout:3000'/article/update/[article_id]':PUT:x-aliyun-apigateway-api-name:article_updatex-aliyun-apigateway-request-parameters:-apiParameterName:'article_id'location:'Path'parameterType:'String'required:'REQUIRED'x-aliyun-apigateway-fc:arn:acs:fc:::services/${serverless-cms.Arn}/functions/${article-update.Arn}/timeout:3000'/article/delete/[article_id]':DELETE:x-aliyun-apigateway-api-name:article_updatex-aliyun-apigateway-request-parameters:-apiParameterName:'article_id'location:'Path'parameterType:'String'required:'REQUIRED'x-aliyun-apigateway-fc:arn:acs:fc:::services/${serverless-cms.Arn}/functions/${article-delete.Arn}/timeout:3000
  26. 在这份配置中,需要注意两个地方:
  27. -
  28. 函数的 Handler 配置,Handler 可以写函数路径,比如`src/function/user/register.handler`表示`src/function/user/`目录中的 register.js 文件中的 handler 方法;
  29. -
  30. API 网关配置中的`/article/detail/[article_id]`Path,这种带参数的 PATH,必须使用`x-aliyun-apigateway-request-parameters`指定 Path 参数。
  31. 接下来,我们就来实现内容管理系统的各个 API,也就是 template.yaml 中定义的各个函数。
  32. <a name="e804a9a2"></a>
  33. #### 用户注册
  34. 用户注册接口定义如下。
  35. -
  36. 请求方法:POST
  37. -
  38. Path`/user/register`
  39. -
  40. Body参数:username 用户名、password 密码。
  41. 整体代码很简单,在入口函数 handler 中,通过 event 得到 API 网关传递过来的 HTTP 请求 body 数据,然后从中得到 usernamepassword,再将用户信息写入数据库。
  42. // src/function/user/registerconstrequire"../../db/client"/**
  43. * 用户注册
  44. * @param {string} username 用户名
  45. * @param {string} password 密码
  46. */asyncfunction register(username, password) await"user"modulefunction (event, context, callback) // event 中获取 API 网关传递 HTTP 请求 body 数据constJSONJSONconst() =>nullsuccesstrue(error) =>successfalsemessage"用户注册失败"

client = ();

{ client.createRow(, { username }, { password }); } .exports.handler = {

body = .parse(.parse(event.toString()).body); { username, password } = body; register(username, password) .then( callback(, { : })) .catch( callback(error, { : , : }) ); };

  1. 代码完成后,就可以将应用部署到函数计算:

部署应用

$ fun deploy Waiting for service serverless-cms to be deployed… … service serverless-cms deploy success Waiting for api gateway ServerlessCMSGroup to be deployed… … api gateway ServerlessCMSGroup deploy success

  1. 部署过程中,如果看到函数服务 serverless-cms API 网关 ServerlessCMSGroup 都成功部署了,就说明应用部署完成。部署完成后,API 网关会提供一个用来测试的 API Endpoint,当然你也可以绑定自定义域名。
  2. 我们可以通过 curl 测试一下:
  3. //a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/user/register \"username=Jack&password=123456""success"true

$ curl http: -X POST \ -d {:}

  1. 返回 `{"success": true}` ,说明用户注册成功。这时在表格存储控制台也可以看到刚注册的用户。
  2. ![](https://s0.lgstatic.com/i/image/M00/94/46/CgqCHmAXxaeAe89-AADufUP1UJA961.png#alt=Drawing%202.png)
  3. <a name="43479813"></a>
  4. #### 用户登录
  5. 完成用户注册函数开发后,就可以接着开发登录。用户登录的接口定义如下。
  6. -
  7. 请求方法:POST
  8. -
  9. Path`/user/login`
  10. -
  11. Body 参数:username 用户名、password 密码。
  12. 登录的逻辑就是根据用户输入的密码是否正确,如果正确就生成一个 token 返回给调用方。代码实现如下:
  13. // src/function/user/loginconstrequire"assert"constrequire'jsonwebtoken'constrequire"../../config"constrequire"../../db/client"/**
  14. * 用户登录
  15. * @param {string} username 用户名
  16. * @param {string} password 密码
  17. */asyncfunction login(username, password) constawait"user"constusernamereturnmodulefunction (event, context, callback) constJSONJSONconst(token) =>nullsuccesstruedata(error) =>successfalsemessage"用户登录失败"

assert = (); jwt = (); { jwt_secret } = (); client = ();

{ user = client.getRow(, { username }); assert(user && user.password === password); token = jwt.sign({ : user.username }, jwt_secret); token; } .exports.handler = { body = .parse(.parse(event.toString()).body); { username, password } = body; login(username, password) .then( callback(, { : , : { token } })) .catch( callback(error, { : , : }) ); };

  1. 将其部署到函数计算后,我们也可以使用 curl 命令进行测试:
  2. //a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/user/login \"username=Jack&password=123456""success"true"data""token""eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkphY2siLCJpYXQiOjE2MTE0OTI2ODF9.c56Xm4RBLYl5yVtR_Vk0IZOL0yijofcyE-P7vjKf4nA"

$ curl http: -X POST \ -d {:,:{:}}

  1. <a name="222f48b2"></a>
  2. #### 身份认证
  3. 在完成了注册登录接口后,我们再来看一下内容管理系统中,身份认证应该怎么实现。
  4. 在 15 讲,我们实现了一个 Express.js 框架的身份认证中间件,用来拦截所有请求,身份认证通过后才能进执行后面的代码逻辑。在内容管理系统中,你也可以参考 Express.js 的思想,实现一个 auth.js 专门用于身份认证,代码如下:
  5. // src/middleware/auth.jsconstrequire"jsonwebtoken"constrequire"../config/index"/**
  6. * 身份认证
  7. * @param {object} event API 网关的 event 对象
  8. * @return {object} 认证通过后返回 user 信息;认证失败则返回 false
  9. */constfunction (event) tryconstJSONifconstJSON" "constreturnreturnfalsecatchreturnfalsemodule

jwt = (); { jwt_secret } = ();

auth = { { data = .parse(event.toString()); (data.headers && data.headers.Authorization) { token = .parse(event.toString()) .headers.Authorization.split() .pop(); user = jwt.verify(token, jwt_secret); user; } ; } (error) { ; } }; .exports = auth;

  1. 其原理很简单,就是从 API 网关的 event 对象中获取 token,然后验证 token 是否正常。如果认证通过,就返回 user 信息,失败就返回 false
  2. 这样在需要身份认证的函数中,你只引入 auth.js 并传入 event 对象就可以了。下面是一个简单的示例:
  3. constrequire'./middleware/auth'modulefunction (event, context, callback) // 使用 auth 进行身份认证constif// 若认证失败则直接返回return'身份认证失败!'// 通过身份认证后的业务逻辑// ...null

auth = (); .exports.handler = {

user = auth(event); (!user) {

  1. callback()

}

callback(); };

  1. 除了登录注册,其他接口都需要身份认证,所以接下来我们就通过实现“发布文章”函数来实际使用 auth.js
  2. <a name="dbe32808"></a>
  3. #### 发布文章
  4. 发布文章的接口定义如下。
  5. -
  6. 请求方法:POST
  7. -
  8. Path`/article/create`
  9. -
  10. Headers 参数: Authorization token
  11. -
  12. Body 参数:titlecontent
  13. 由于登录后才能发布文章,所以要先通过登录接口获取 token,然后调用 `/article/create` 接口时,再将 token 放在 HTTP Headers 参数中。发布文章的代码实现如下:
  14. // src/function/article/authconstrequire"uuid"constrequire"../../middleware/auth"constrequire"../../db/client"/**
  15. * 创建文章
  16. * @param {string} username 用户名
  17. * @param {string} title 文章标题
  18. * @param {string} content 文章内容
  19. */asyncfunction createArticle(username, title, content) constconstnewDateawait"article"create_dateupdate_datereturnmodulefunction (event, context, callback) // 身份认证constif// 若认证失败则直接返回return"身份认证失败"// 从 user 中获取 usernameconstconstJSONJSONconst() =>nullsuccesstrue(error) =>successfalsemessage"创建文章失败"

uuid = (); auth = (); client = ();

{ article_id = uuid.v4(); now = ().toLocaleString(); client.createRow( , { article_id, }, { username, title, content, : now, : now, } ); article_id; } .exports.handler = {

user = auth(event); (!user) {

  1. callback();

}

{ username } = user; body = .parse(.parse(event.toString()).body); { title, content } = body; createArticle(username, title, content) .then( callback(, { : , }) ) .catch( callback(error, { : , : , }) ); };

  1. 首先是使用 auth.js 进行身份认证,认证通过后就可以从 user 中获取 username。然后再从请求体中获取文章标题和文章内容数据,将其存入数据库。
  2. 接下来我们依旧可以将函数部署和使用 curl 进行测试:
  3. //a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/article/create \"title=这是文章标题&content=内容内容内容......""Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkphY2siLCJpYXQiOjE2MTE0OTI2ODF9.c56Xm4RBLYl5yVtR_Vk0IZOL0yijofcyE-P7vjKf4nA""success"true"data""article_id""d4b9bad8-a0ed-499d-b3c6-c57f16eaa193"

$ curl http: -X POST \ -d \ -H {:,:{:}}

  1. 在测试时,我们需要将 token 放在 HTTP 请求头的 Authorization 属性中。文章发布成功后,你就可以在表格存储中看到对应的数据了。
  2. ![](https://s0.lgstatic.com/i/image/M00/94/3B/Ciqc1GAXxceARRiPAACAwtaSp94526.png#alt=Drawing%203.png)
  3. <a name="1a6d4ba9"></a>
  4. #### 查询文章
  5. 发布文章的接口开发完成后,我们继续开发一个查询文章的接口,这样就可以查询出刚才创建的文章。查询文章接口定义如下。
  6. -
  7. 请求方法:GET
  8. -
  9. Path`/article/detail/[article_id]`
  10. -
  11. Headers 参数: Authorization token
  12. 在查询文章接口中,我们需要在 Path 中定义文章 ID 参数,即 article_id。这样在函数代码中,你就可以通过 event 对象的 pathParameters 中获取 article_id 参数,然后根据 article_id 来查询文章详情了。完整代码如下:
  13. constrequire"uuid"constrequire"../../middleware/auth"constrequire"../../db/client"/**
  14. * 获取文章详情
  15. * @param {string} title 文章 ID
  16. */asyncfunction getArticle(article_id) constawait"article"returnmodulefunction (event, context, callback) // 身份认证constif// 若认证失败则直接返回return"身份认证失败"// 从 event 对象中获取文章 ID<br />
  17. const article_id = JSON.parse(event.toString()).pathParameters['article_id'];<br />
  18. getArticle(article_id)<br />
  19. .then((detail) =><br />
  20. callback(null, {<br />
  21. success: true,<br />
  22. data: detail<br />
  23. })<br />
  24. )<br />
  25. .catch((error) =><br />
  26. callback(error, {<br />
  27. success: false,<br />
  28. message: "创建文章失败",<br />
  29. })<br />
  30. );<br />
  31. };<br />

uuid = (); auth = (); client = ();

{ res = client.getRow( , { article_id, }, ); res; } .exports.handler = {

user = auth(event); (!user) {

  1. callback();

}

  1. 开发完成后,我们可以将其部署到函数计算,再用 curl 命令进行测试:
  2. //a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/article/detail/d4b9bad8-a0ed-499d-b3c6-c57f16eaa193 \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkphY2siLCJpYXQiOjE2MTE0OTI2ODF9.c56Xm4RBLYl5yVtR_Vk0IZOL0yijofcyE-P7vjKf4nA""success"true"data""article_id""d4b9bad8-a0ed-499d-b3c6-c57f16eaa193""content""内容内容内容......""create_date""1/24/2021, 2:05:46 PM""title""这是文章标题""update_date""1/24/2021, 2:05:46 PM""username""Jack"

$ curl http: -H {:,:{:,:,:,:,:,:}}

  1. 如上所示,查询文章的接口按照预期返回了文章详情。
  2. <a name="9e57007b"></a>
  3. #### 更新文章
  4. 更新文章的 API Path 参数和查询文章一样,都需要 Path 中定义 article_id。而其 body 参数则与创建文章相同。此外,更新文章的请求 method PUT,因为在 Restful API 规范中,我们通常使用 POST 来表示创建, 使用 PUT 来表示更新。
  5. 更新文章的接口定义如下。
  6. -
  7. 请求方法:PUT
  8. -
  9. Path`/article/update/[article_id]`
  10. -
  11. Headers 参数: Authorization token
  12. -
  13. Body 参数:titlecontent
  14. 更新文章的逻辑就是根据 article_id 去更新一行数据。代码如下:
  15. constrequire"../../middleware/auth"constrequire"../../db/client"/**
  16. * 更新文章
  17. * @param {string} article_id 待更新的文章 ID
  18. * @param {string} title 文章标题
  19. * @param {string} content 文章内容
  20. */asyncfunction updateArticle(article_id, title, content) constnewDateawait"article"update_datemodulefunction (event, context, callback) // 身份认证constif// 若认证失败则直接返回return"身份认证失败"constJSON// 从 event 对象的 pathParameters 中获取 Path 参数const'article_id'constJSON// 从 event 对象的 body 中获取请求体参数const() =>nullsuccesstrue(error) =>successfalsemessage"更新文章失败"

auth = (); client = ();

{ now = ().toLocaleString(); client.updateRow( , { article_id, }, { title, content, : now, } ); } .exports.handler = {

user = auth(event); (!user) {

  1. callback();

} eventObject = .parse(event.toString())

article_id = eventObject.pathParameters[]; body = .parse(eventObject.body);

{ title, content } = body; updateArticle(article_id, title, content) .then( callback(, { : , }) ) .catch( callback(error, { : , : , }) ); };

  1. 开发并部署完成后,使用 curl 命令进行测试:
  2. //a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/article/update/d4b9bad8-a0ed-499d-b3c6-c57f16eaa193 \"title=这是文章标题&content=更新的内容......""Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkphY2siLCJpYXQiOjE2MTE0OTI2ODF9.c56Xm4RBLYl5yVtR_Vk0IZOL0yijofcyE-P7vjKf4nA""success"true

$ curl http: -X PUT \ -d \ -H {:}

  1. 返回 `{"success":true}` 则说明更新成功。
  2. <a name="f1ce042a"></a>
  3. #### 删除文章
  4. 最后就还是一个删除文章的 API 了。删除文章的 API 也需要在 Path 中定义 article_id 参数,并且其 HTTP method DELETE。具体接口定义如下。
  5. -
  6. 请求方法:DELETE
  7. -
  8. Path`/article/delete/[article_id]`
  9. -
  10. Headers 参数: Authorization token
  11. 删除文章很简单,就是根据 article_id 删除一行数据,代码如下:
  12. constrequire"uuid"constrequire"../../middleware/auth"constrequire"../../db/client"/**
  13. * 删除文章
  14. * @param {string} title 文章 ID
  15. */asyncfunction deleteArticle(article_id) constawait"article"returnmodulefunction (event, context, callback) // 身份认证constif// 若认证失败则直接返回return"身份认证失败"// 从 event 对象中获取文章 ID<br />
  16. const article_id = JSON.parse(event.toString()).pathParameters['article_id'];<br />
  17. deleteArticle(article_id)<br />
  18. .then(() =><br />
  19. callback(null, {<br />
  20. success: true,<br />
  21. })<br />
  22. )<br />
  23. .catch((error) =><br />
  24. callback(error, {<br />
  25. success: false,<br />
  26. message: "删除文章失败",<br />
  27. })<br />
  28. );<br />
  29. };<br />

uuid = (); auth = (); client = ();

{ res = client.deleteRow( , { article_id, }, ); res; } .exports.handler = {

user = auth(event); (!user) {

  1. callback();

}

  1. 同样我们可以通过 curl 命令进行测试:
  2. //a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/article/delete/d4b9bad8-a0ed-499d-b3c6-c57f16eaa193 \"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkphY2siLCJpYXQiOjE2MTE0OTI2ODF9.c56Xm4RBLYl5yVtR_Vk0IZOL0yijofcyE-P7vjKf4nA""success"true

curl http: -X DELETE \ -H {:} ```

删除成功后,再去表格存储中就找不到这行记录了。至此,内容管理系统的 Restful API 就开发完毕了。

总结

可以看到,基于 Serverless 开发 Restful API 的整个代码非常简单,每个函数只负责一个独立的业务,职责单一、逻辑清晰。关于这一讲,我想强调这样几个重点:

  • 基于 Serverless 开发 API 时,建议你使用 API 网关进行 API 的管理;

  • 对于数据库等第三方服务,建议对其基本操作进行封装,这样更方便进行扩展;

  • Serverless 函数需要保持简单、独立、单一职责。

最后,我留给你的作业就是,亲自动手实现一个基于 Serverless 的具有 Restful API 的内容管理系统。我们下一讲见。