本资源由 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 的内容管理系统架构图:
图中主要表达的意思是: 通过 API 网关承接用户请求,并驱动函数执行。每个函数分别实现一个具体功能,并通过 JWT 实现身份认证,最后表格存储作为数据库。
其中,数据库中存储的数据主要是用户数据和文章数据。假设用户有 username(用户名) 和 password(密码) 两个属性;文章有 article_id(文章 ID)、username(创建者)、title(文章标题)、content(文章内容)、create_date(创建时间)、update_date(更新时间)这几个属性。
接下来,你可以在表格存储中创建对应的数据表(你可以在表格存储控制台创建,也可以直接用我提供的这段代码进行创建):
// index.jsconstrequire”tablestore”// 初始化 TableStore clientconstnewaccessKeyId’
- 创建 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 = { : { : , : [
{
: ,
: TableStore.PrimaryKeyType.STRING,
},
], : [
{
: ,
: TableStore.DefinedColumnType.DCT_STRING,
},
], },
: { : {
: ,
: ,
}, }, : {
: ,
: , }, }; client.createTable(table); }
{ table = { : { : , : [
{
: ,
: TableStore.PrimaryKeyType.STRING,
},
], : [
{
: ,
: TableStore.DefinedColumnType.DCT_STRING,
},
{
: ,
: TableStore.DefinedColumnType.DCT_STRING,
},
{
: ,
: TableStore.DefinedColumnType.DCT_STRING,
},
{
: ,
: TableStore.DefinedColumnType.DCT_STRING,
},
{
: ,
: TableStore.DefinedColumnType.DCT_STRING,
},
], },
: { : {
: ,
: ,
}, }, : {
: ,
: , }, }; client.createTable(table); } ( { createUserTable(); createArticleTable(); })(); ```
这段代码主要创建了 user 和 article 两张表,其中 user 表的主键是 username,article 表的主键是 article_id,主键的作用是方便查询。除了主键,我还定义了几个列。其实对于表格存储,默认也可以不创建列,表格存储是宽表,除主键外,数据列可以随意扩展。
在完成了数据库表的创建后,我们就可以开始进行系统实现了。
内容管理系统的实现
为了方便你学习,我为你提供了完整代码(代码地址),你可以参考着学习。
//github.com/nodejh/serverless-class15``` $ git clone https: $ cd /cms
整个代码目录结构如下:
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
其中,所有业务代码都放在 src 目录中:
-
config/index.js 是配置文件,里面包含身份凭证等配置信息;
-
db/client.js 对表格存储的增删改查操作进行了封装,方便在函数中使用(将数据库的操作封装还有一个好处是,如果你之后想要迁移到其他数据库,只要修改 db/client.js 中的逻辑,不用修改业务代码);
-
middleware 目录中是一些中间件,比如 auth.js,用于身份认证;
-
functions 目录中就是所有函数,登录、注册、创建文章等,每个功能分别对应一个函数;
-
template.yaml 是应用配置文件,包括函数和 API 网关的配置。
根据前面梳理的系统功能,我们需要实现以下几个 API:
| 用户注册 | POST /user/register |
| --- | --- |
| 用户登录 | POST /user/login |
| 发布文章 | POST /article/create |
| 查询文章 | GET /article/detail/[article_id] |
| 更新文章 | POST /article/update |
| 删除文章 | PUT /article/delete/[article_id] |
每个 API 对应一个具体的函数,每个函数也都有一个与之对应的 API 网关触发器。由于这些函数属于同一个应用,所以我们可以通过一个 template.yaml 来定义所有函数。同时也可以在 template.yaml 中定义函数的 API 网关触发器,这样部署函数时,就会自动创建 API 网关。
内容管理系统的 template.yaml 格式如下:
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
**template.yaml 主要分为两部分:** 函数定义和 API 网关定义,每个函数都有一个与之对应的 API 网关。我们用 serverless-cms 服务来表示内容管理系统这个应用,服务内的所有函数都是内容管理系统的函数。同理,ServerlessCMSGroup 这个 API 网关分组中的所有 API 都是内容管理系统的 API。
完整的 template.yaml 配置如下:
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
在这份配置中,需要注意两个地方:
-
函数的 Handler 配置,Handler 可以写函数路径,比如`src/function/user/register.handler`表示`src/function/user/`目录中的 register.js 文件中的 handler 方法;
-
API 网关配置中的`/article/detail/[article_id]`Path,这种带参数的 PATH,必须使用`x-aliyun-apigateway-request-parameters`指定 Path 参数。
接下来,我们就来实现内容管理系统的各个 API,也就是 template.yaml 中定义的各个函数。
<a name="e804a9a2"></a>
#### 用户注册
用户注册接口定义如下。
-
请求方法:POST。
-
Path:`/user/register`
-
Body参数:username 用户名、password 密码。
整体代码很简单,在入口函数 handler 中,通过 event 得到 API 网关传递过来的 HTTP 请求 body 数据,然后从中得到 username、password,再将用户信息写入数据库。
// src/function/user/registerconstrequire"../../db/client"/**
* 用户注册
* @param {string} username 用户名
* @param {string} password 密码
*/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, { : , : }) ); };
代码完成后,就可以将应用部署到函数计算:
部署应用
$ 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
部署过程中,如果看到函数服务 serverless-cms 和 API 网关 ServerlessCMSGroup 都成功部署了,就说明应用部署完成。部署完成后,API 网关会提供一个用来测试的 API Endpoint,当然你也可以绑定自定义域名。
我们可以通过 curl 测试一下:
//a88f7e84f71749958100997b77b3e2f6-cn-beijing.alicloudapi.com/user/register \"username=Jack&password=123456""success"true
$ curl http: -X POST \ -d {:}
返回 `{"success": true}` ,说明用户注册成功。这时在表格存储控制台也可以看到刚注册的用户。
![](https://s0.lgstatic.com/i/image/M00/94/46/CgqCHmAXxaeAe89-AADufUP1UJA961.png#alt=Drawing%202.png)
<a name="43479813"></a>
#### 用户登录
完成用户注册函数开发后,就可以接着开发登录。用户登录的接口定义如下。
-
请求方法:POST。
-
Path:`/user/login`
-
Body 参数:username 用户名、password 密码。
登录的逻辑就是根据用户输入的密码是否正确,如果正确就生成一个 token 返回给调用方。代码实现如下:
// src/function/user/loginconstrequire"assert"constrequire'jsonwebtoken'constrequire"../../config"constrequire"../../db/client"/**
* 用户登录
* @param {string} username 用户名
* @param {string} password 密码
*/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, { : , : }) ); };
将其部署到函数计算后,我们也可以使用 curl 命令进行测试:
//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 {:,:{:}}
<a name="222f48b2"></a>
#### 身份认证
在完成了注册登录接口后,我们再来看一下内容管理系统中,身份认证应该怎么实现。
在 15 讲,我们实现了一个 Express.js 框架的身份认证中间件,用来拦截所有请求,身份认证通过后才能进执行后面的代码逻辑。在内容管理系统中,你也可以参考 Express.js 的思想,实现一个 auth.js 专门用于身份认证,代码如下:
// src/middleware/auth.jsconstrequire"jsonwebtoken"constrequire"../config/index"/**
* 身份认证
* @param {object} event API 网关的 event 对象
* @return {object} 认证通过后返回 user 信息;认证失败则返回 false
*/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;
其原理很简单,就是从 API 网关的 event 对象中获取 token,然后验证 token 是否正常。如果认证通过,就返回 user 信息,失败就返回 false。
这样在需要身份认证的函数中,你只引入 auth.js 并传入 event 对象就可以了。下面是一个简单的示例:
constrequire'./middleware/auth'modulefunction (event, context, callback) // 使用 auth 进行身份认证constif// 若认证失败则直接返回return'身份认证失败!'// 通过身份认证后的业务逻辑// ...null
auth = (); .exports.handler = {
user = auth(event); (!user) {
callback()
}
callback(); };
除了登录注册,其他接口都需要身份认证,所以接下来我们就通过实现“发布文章”函数来实际使用 auth.js。
<a name="dbe32808"></a>
#### 发布文章
发布文章的接口定义如下。
-
请求方法:POST。
-
Path:`/article/create`
-
Headers 参数: Authorization token。
-
Body 参数:title、content。
由于登录后才能发布文章,所以要先通过登录接口获取 token,然后调用 `/article/create` 接口时,再将 token 放在 HTTP Headers 参数中。发布文章的代码实现如下:
// src/function/article/authconstrequire"uuid"constrequire"../../middleware/auth"constrequire"../../db/client"/**
* 创建文章
* @param {string} username 用户名
* @param {string} title 文章标题
* @param {string} content 文章内容
*/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) {
callback();
}
{ username } = user; body = .parse(.parse(event.toString()).body); { title, content } = body; createArticle(username, title, content) .then( callback(, { : , }) ) .catch( callback(error, { : , : , }) ); };
首先是使用 auth.js 进行身份认证,认证通过后就可以从 user 中获取 username。然后再从请求体中获取文章标题和文章内容数据,将其存入数据库。
接下来我们依旧可以将函数部署和使用 curl 进行测试:
//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 {:,:{:}}
在测试时,我们需要将 token 放在 HTTP 请求头的 Authorization 属性中。文章发布成功后,你就可以在表格存储中看到对应的数据了。
![](https://s0.lgstatic.com/i/image/M00/94/3B/Ciqc1GAXxceARRiPAACAwtaSp94526.png#alt=Drawing%203.png)
<a name="1a6d4ba9"></a>
#### 查询文章
发布文章的接口开发完成后,我们继续开发一个查询文章的接口,这样就可以查询出刚才创建的文章。查询文章接口定义如下。
-
请求方法:GET。
-
Path:`/article/detail/[article_id]`
-
Headers 参数: Authorization token。
在查询文章接口中,我们需要在 Path 中定义文章 ID 参数,即 article_id。这样在函数代码中,你就可以通过 event 对象的 pathParameters 中获取 article_id 参数,然后根据 article_id 来查询文章详情了。完整代码如下:
constrequire"uuid"constrequire"../../middleware/auth"constrequire"../../db/client"/**
* 获取文章详情
* @param {string} title 文章 ID
*/asyncfunction getArticle(article_id) constawait"article"returnmodulefunction (event, context, callback) // 身份认证constif// 若认证失败则直接返回return"身份认证失败"// 从 event 对象中获取文章 ID<br />
const article_id = JSON.parse(event.toString()).pathParameters['article_id'];<br />
getArticle(article_id)<br />
.then((detail) =><br />
callback(null, {<br />
success: true,<br />
data: detail<br />
})<br />
)<br />
.catch((error) =><br />
callback(error, {<br />
success: false,<br />
message: "创建文章失败",<br />
})<br />
);<br />
};<br />
uuid = (); auth = (); client = ();
{ res = client.getRow( , { article_id, }, ); res; } .exports.handler = {
user = auth(event); (!user) {
callback();
}
开发完成后,我们可以将其部署到函数计算,再用 curl 命令进行测试:
//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 {:,:{:,:,:,:,:,:}}
如上所示,查询文章的接口按照预期返回了文章详情。
<a name="9e57007b"></a>
#### 更新文章
更新文章的 API Path 参数和查询文章一样,都需要 Path 中定义 article_id。而其 body 参数则与创建文章相同。此外,更新文章的请求 method 是 PUT,因为在 Restful API 规范中,我们通常使用 POST 来表示创建, 使用 PUT 来表示更新。
更新文章的接口定义如下。
-
请求方法:PUT。
-
Path:`/article/update/[article_id]`
-
Headers 参数: Authorization token。
-
Body 参数:title、content。
更新文章的逻辑就是根据 article_id 去更新一行数据。代码如下:
constrequire"../../middleware/auth"constrequire"../../db/client"/**
* 更新文章
* @param {string} article_id 待更新的文章 ID
* @param {string} title 文章标题
* @param {string} content 文章内容
*/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) {
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, { : , : , }) ); };
开发并部署完成后,使用 curl 命令进行测试:
//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 {:}
返回 `{"success":true}` 则说明更新成功。
<a name="f1ce042a"></a>
#### 删除文章
最后就还是一个删除文章的 API 了。删除文章的 API 也需要在 Path 中定义 article_id 参数,并且其 HTTP method 是 DELETE。具体接口定义如下。
-
请求方法:DELETE。
-
Path:`/article/delete/[article_id]`
-
Headers 参数: Authorization token,
删除文章很简单,就是根据 article_id 删除一行数据,代码如下:
constrequire"uuid"constrequire"../../middleware/auth"constrequire"../../db/client"/**
* 删除文章
* @param {string} title 文章 ID
*/asyncfunction deleteArticle(article_id) constawait"article"returnmodulefunction (event, context, callback) // 身份认证constif// 若认证失败则直接返回return"身份认证失败"// 从 event 对象中获取文章 ID<br />
const article_id = JSON.parse(event.toString()).pathParameters['article_id'];<br />
deleteArticle(article_id)<br />
.then(() =><br />
callback(null, {<br />
success: true,<br />
})<br />
)<br />
.catch((error) =><br />
callback(error, {<br />
success: false,<br />
message: "删除文章失败",<br />
})<br />
);<br />
};<br />
uuid = (); auth = (); client = ();
{ res = client.deleteRow( , { article_id, }, ); res; } .exports.handler = {
user = auth(event); (!user) {
callback();
}
同样我们可以通过 curl 命令进行测试:
//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 的内容管理系统。我们下一讲见。