跳到主要内容

云引擎 SDK 使用指南

信息

这篇文档是关于云引擎 SDK 的深入介绍,如需了解云函数和 Hook 的用法请看 云函数和 Hook 开发指南

云引擎 SDK 通常基于 数据存储 服务的 SDK,并提供了云函数和 Hook 等额外能力,供开发者在云引擎上更方便地开发后端应用。

接入云引擎 SDK

我们的示例项目默认已经接入了 SDK,如果你需要将云引擎 SDK 接入到已有的项目中,可以按照下面的步骤操作:

npm install leanengine leancloud-storage

然后在代码中将云引擎中间件挂载到 Express 框架上:

app.js
var express = require("express");
var AV = require("leanengine");

AV.init({
appId: process.env.LEANCLOUD_APP_ID,
appKey: process.env.LEANCLOUD_APP_KEY,
masterKey: process.env.LEANCLOUD_APP_MASTER_KEY,
});

var app = express();
app.use(AV.express());
app.listen(process.env.LEANCLOUD_APP_PORT);

其中,AV.express 接受一个可选参数 optionsoptions 是一个对象,目前支持以下两个可选属性:

  • onError:全局错误处理函数,云函数(包括 Hook 函数)抛出异常时会调用该函数。该函数的使用场景包括统一发送错误报告。
  • ignoreInvalidSessionToken:布尔值,为真时忽略客户端发来的错误的 sessionTokenX-LC-session 头),为假时抛出 401 错误 {"code": 211, "error": "Verify sessionToken failed, maybe login expired: ..."}。客户端 SDK 发送请求时会统一发送 X-LC-session 头(其中指定了 sessionToken),sessionToken 可能因种种原因失效,而云函数在很多情况下并不关心 sessionToken。因此,云引擎提供了 ignoreInvalidSessionToken 这个选项,设为真时忽略 sessionToken 错误。反之,如果该选项设为假,客户端收到相应报错时,需要重新登录。

关于云引擎 SDK 的详细 API 文档见 LeanEngine Node SDK · API.md

点击展开 Koa 框架接入方法
app.js
var koa = require("koa");
var AV = require("leanengine");

AV.init({
appId: process.env.LEANCLOUD_APP_ID,
appKey: process.env.LEANCLOUD_APP_KEY,
masterKey: process.env.LEANCLOUD_APP_MASTER_KEY,
});

var app = koa();
app.use(AV.koa());
app.listen(process.env.LEANCLOUD_APP_PORT);
点击展开 Node SDK 不同版本的差异

Node SDK 的历史版本:

  • 1.x:彻底废弃了全局的 currentUser,依赖的 JavaScript 也升级到了 1.x 分支,支持了 Koa 和 Node.js 4.x 及以上版本。
  • 2.x:提供了对 Promise 风格的云函数、Hook 写法的支持,移除了一些被弃用的特性(AV.Cloud.httpRequest),不再支持 Backbone 风格的回调函数。
  • 3.x推荐使用 的版本,指定 JavaScript SDK 为 peer dependency(允许自定义 JS SDK 的版本),升级 JS SDK 到 3.x。

详见 Node.js SDK 的 更新日志

你可以在 GitHub 上找到 Node SDK 的源代码。

使用数据存储服务

接入 SDK 后,在云引擎中你就可以调用 数据存储 服务作为数据库来存储数据,或者使用文件、短信、推送等功能。可以查看数据存储服务对应语言的文档了解详情。

数据存储相关功能可以在云函数和 Hook 中使用,也可以在程序的其他部分(如自行选用的 Web 框架)中使用。

使用超级权限

因为云引擎运行在可信的服务器端环境中,所以可以使用 Master Key(超级权限)跳过 ACL 和 Class 权限的检查,没有限制地修改数据存储中的数据;还可以使用一些仅限 Master Key 调用的管理员接口,如 遍历 Class(scan)

所以你可以全局开启超级权限(Master Key),这样会跳过包括 ACL 和 Class 权限在内的检查,让你自由地操作所有云存储中的数据,也允许调用一些仅供 Master Key 使用的 API。

全局开启 Master Key:

sever.js
AV.Cloud.useMasterKey();

如果没有添加这些代码,默认是没有超级权限的,这意味着在云引擎中你也不能修改被 ACL 保护的数据,你需要在进行操作时手动指定 sessionToken,让操作以这个用户的权限来执行:

const post = new Post();
post.save(
{ author: user }
// 或者使用 request.sessionToken(网站托管中需启用 `Cloud.CookieSession`)
// {
// sessionToken: user.getSessionToken()
// }
);

或者你也可单独对某一个操作使用 Master Key,跳过权限检查:

post.destroy({ useMasterKey: true });

当然你也可以在启用了 Master Key 的情况下使用 useMasterKey: false 来对单个操作关掉 Master Key。

那么究竟是否应该使用 Master Key 呢,我们的建议如下:

  • 如果你的云引擎代码中特权操作比较多、操作不属于用户的全局数据比较多,那么建议全局开启 Master Key,并自行做好对于用户请求的权限检查。
  • 如果你的云引擎代码中的请求通常和单个用户自己的数据相关、需要遵守 ACL,那么建议不开启 Master Key,将用户请求的 sessionToken 传入数据修改的相关操作。

关于云引擎上的权限问题,还可以参考 ACL 权限管理开发指南在云引擎中使用 ACL

用户状态管理

警告

因云引擎属于多主机、多进程的运行环境,因此内存型的 Session 是无法正确工作的(如 Node.js 的 express-session 默认的 MemoryStore、PHP 内建的 $_SESSION)。

使用 HTTP Header

如果你的页面主要是由浏览器端渲染,那么建议在前端使用 SDK 登录用户,调用 SDK 的接口获取 Session Token,通过 HTTP Header 等方式将 Session Token 发送给后端。

例如,在前端登录用户并通过 user.getSessionToken() 获取 Session Token 并发送给后端:

AV.User.login(user, pass).then((user) => {
return fetch("/profile", {
headers: {
"X-LC-Session": user.getSessionToken(),
},
});
});

相应的后端 Node.js 代码:

app.get("/profile", function (req, res) {
AV.User.become(req.headers["x-lc-session"])
.then((user) => {
res.send(user);
})
.catch((err) => {
res.send({ error: err.message });
});
});

app.post("/todos", function (req, res) {
var todo = new Todo();
todo
.save(req.body, { sessionToken: req.headers["x-lc-session"] })
.then(() => {
res.send(todo);
})
.catch((err) => {
res.send({ error: err.message });
});
});

CookieSession

如果你的页面主要由服务端渲染,可以使用我们在部分 SDK 中提供的 Cookie Session 组件,它可以将数据存储服务中的 Session Token 存储在 Cookie 中,简化服务器端对于用户登录状态的管理。

危险

使用 Cookie 作为鉴权方式需要注意防范 防御 CSRF 攻击

业界通常使用 CSRF Token 来防御 CSRF 攻击,你需要传递给客户端一个随机字符串(即 CSRF Token,可通过 Cookie 传递),客户端在每个有副作用的请求中都要将 CSRF 包含在请求正文或 Header 中,服务器端需要校验这个 CSRF Token 是否正确。

如果你的页面主要是由服务器端渲染(例如使用 EJS、Pug),在前端不需要使用 JavaScript SDK 进行数据操作,那么可以使用 AV.Cloud.CookieSession 中间件,在 Cookie 中维护用户状态:

// Express
app.use(
AV.Cloud.CookieSession({
secret: "my secret",
maxAge: 3600000,
fetchUser: true,
})
);
// Koa
app.use(
AV.Cloud.CookieSession({
framework: "koa",
secret: "my secret",
maxAge: 3600000,
fetchUser: true,
})
);

你需要传入一个 secret 用于签名 Cookie(必须提供),这个中间件会将 AV.User 的登录状态信息记录到 Cookie 中,用户下次访问时自动检查用户是否已经登录,如果已经登录,可以通过 req.currentUser 获取当前登录用户。

AV.Cloud.CookieSession 支持的选项包括:

  • fetchUser:是否自动 fetch 当前登录的 AV.User 对象。默认为 false。如果设置为 true,每个 HTTP 请求都将发起一次 API 调用来 fetch 用户对象。如果设置为 false,默认只可以访问 req.currentUserid_User 表记录的 objectId)和 sessionToken 属性,你可以在需要时再手动 fetch 整个用户。
  • name:Cookie 的名字,默认为 avos.sess
  • maxAge:Cookie 的过期时间。单位为毫秒。

在 Node SDK 中不再允许通过 AV.User.current() 获取登录用户的信息,而是需要你:

  • 通过 request.currentUser 获取用户信息。
  • 在后续的方法调用显式传递 user 对象。
点击展开一个具有登录功能的站点的例子
app.post("/login", function (req, res) {
AV.User.logIn(req.body.username, req.body.password).then(
function (user) {
res.saveCurrentUser(user); // save cookie
res.redirect("/profile");
},
function (error) {
res.redirect("/login");
}
);
});

app.get("/profile", function (req, res) {
if (req.currentUser) {
res.send(req.currentUser);
} else {
res.redirect("/login");
}
});

app.get("/logout", function (req, res) {
req.currentUser.logOut();
res.clearCurrentUser(); // clear cookie
res.redirect("/profile");
});
点击展开浏览器对跨域 Cookie 的限制(SameSite

Chrome 80 起 SameSite 的默认值为 Lax,如果你的应用的前端没部署在云引擎上,又需要向云引擎发送携带 Cookie 的 POST 请求,那么需要设置 SameSitenone

AV.Cloud.CookieSession 会将所有参数都传递给浏览器的 cookies.set(),所以你可以将 sameSite 传入:

AV.Cloud.CookieSession({ sameSite: "none" });

注意:

  • SameSite 要求与 Secure 标记一同发送,因此请确保你的客户端是通过 HTTPS 协议访问云引擎的。
  • 请仅在有必要的时候设置 SameSitenone,以免平白增加 CSRF 风险。

FAQ

如何使用 SDK 重定向到 HTTPS?

我们目前推荐在绑定自定义域名时勾选「强制 HTTPS」而不是使用 SDK 中的重定向中间件。

信息

不过,「强制 HTTPS」选项目前只支持独立 IP。 如果使用加速节点,仍需在项目代码层面实现重定向。

点击展开关于 SDK 中重定向到 HTTPS 的用法(不推荐)

一些 SDK 提供了重定向至 HTTPS 的中间件,部署并发布到生产环境之后,访问你的 LeanEngine 网站都会强制通过 HTTPS 访问。

Node.js(Express):

app.enable("trust proxy");
app.use(AV.Cloud.HttpsRedirect());

Node.js(Koa):

app.proxy = true;
app.use(AV.Cloud.HttpsRedirect({ framework: "koa" }));

如何打印 SDK 发出的网络请求?

你可以通过设置一个 DEBUG=leancloud:request 的环境变量来打印由 SDK 发出的网络请求。在本地调试时你可以通过这样的命令启动程序:

env DEBUG=leancloud:request lean up

当有对服务端的调用时,你可以看到类似这样的日志:

leancloud:request request(0) +0ms GET https://{{host}}/1.1/classes/Todo?&where=%7B%7D&order=-createdAt { where: '{}', order: '-createdAt' }
leancloud:request response(0) +220ms 200 {"results":[{"content":"1","createdAt":"2016-08-09T06:18:13.028Z","updatedAt":"2016-08-09T06:18:13.028Z","objectId":"57a975a55bbb5000643fb690"}]}

我们不建议在线上生产环境开启这个日志,否则将会打印大量的日志。如有必要,可以指定 DEBUG=leancloud:request:error,只打印出错的网络请求。

为什么 Pointer 字段中的数据没有完整地发给客户端?

将 JavaScript SDK 和 Node SDK 升级到 3.0 以上版本可以彻底解决该问题。

云函数在响应时会调用到 AV.Object#toJSON 方法,将结果序列化为 JSON 对象返回给客户端。在早期版本中 AV.Object#toJSON 方法为了防止循环引用,当遇到属性是 Pointer 类型会返回 Pointer 元信息,不会将 include 的其他字段添加进去,我们在 JavaScript SDK 3.0 中对序列化相关的逻辑做了重新设计,将 JavaScript SDK 和 Node SDK 升级到 3.0 以上版本便可以彻底解决该问题

如果暂时无法升级 SDK 版本,可以通过这样的方式绕过:

AV.Cloud.define("querySomething", function (req, res) {
var query = new AV.Query("Something");
// user 是 Something 表的一个 Pointer 字段
query.include("user");
query
.find()
.then(function (results) {
// 手动进行一次序列化
results.forEach(function (result) {
result.set(
"user",
result.get("user") ? result.get("user").toJSON() : null
);
});
// 再返回查询结果给客户端
res.success(results);
})
.catch(res.error);
});

RPC 调用云函数时,为什么会返回预期之外的空对象?

使用 Node SDK 定义的云函数,如果返回一个不是 AVObject 的值,比如字符串、数字,RPC 调用得到的是空对象({})。 类似地,如果返回一个包含非 AVObject 成员的数组,RPC 调用的结果中该数组的相应成员也会被序列化为 {}。 这个问题将在 Node SDK 的下一个大版本(4.0)中修复。 目前绕过这一个问题的方法是将返回结果放在对象({})中返回。