阅读(4464) (0)

Moralis 云函数

2022-05-12 09:21:04 更新

定义云函数

对于复杂的应用程序,有时您需要一些不在移动设备上运行的逻辑。 ​Cloud Code​ 使这成为可能。

Cloud Code​ 易于使用,因为它建立在支持数千个应用程序的同一个 ​Moralis JavaScript SDK​ 之上。 唯一的区别是此代码在您的 ​Moralis Dapp​ 中运行,而不是在用户的移动设备上运行。 当您更新您的云代码时,它会立即可供所有移动环境使用。 您不必等待应用程序的新版本。 这使您可以即时更改应用程序行为并更快地添加新功能。

即使您只熟悉移动开发,我们也希望您会发现 ​Cloud Code​ 简单易用。

您可以直接在仪表板上编辑云代码或设置 IDE。

spaces_-MVStbACGLCycg7J5WQ2_uploads_git-blob-39cb7ed6dc5927c2c657af390873c6f857d11f90_Screenshot 2022-04-08 at 2

spaces_-MVStbACGLCycg7J5WQ2_uploads_git-blob-c2cb234a2f746c35d0b083b2209a544e80c5c029_Moralis_dashboard_cloudfunction

让我们看一个稍微复杂一点的例子,其中 ​Cloud Code​ 很有用。 在云中进行计算的一个原因是,如果您只需要一点点信息,就不必将大量对象列表发送到设备。

例如,假设您正在编写一个让人们评论电影的应用程序。 单个 ​Review ​对象可能如下所示:

{
  "movie": "The Matrix",
  "stars": 5,
  "comment": "Too bad they never made any sequels."
}

如果您想查找 The Matrix 的平均星级数,您可以查询所有评论以及设备上的平均星级数。 但是,当您只需要一个号码时,这会占用大量带宽。 使用 ​Cloud Code​,我们可以只传递电影的名称,并返回平均星级。

Cloud Functions​ 接受请求对象上的 JSON 参数字典,因此我们可以使用它来传递电影名称。 整个 ​Moralis JavaScript SDK​ 在云环境中可用,因此我们可以使用它来查询 Review 对象。 实现 averageStars 的代码如下所示:

Moralis.Cloud.define("averageStars", async (request) => {
  const query = new Moralis.Query("Review");
  query.equalTo("movie", request.params.movie);
  const results = await query.find();
  let sum = 0;
  for (let i = 0; i < results.length; ++i) {
    sum += results[i].get("stars");
  }
  return sum / results.length;
});

使用 averageStars 和 hello 的唯一区别是我们必须在调用 ​Cloud Function​ 时提供将在 ​request.params.movie​ 中访问的参数。 

全局包

以下包在 ​Cloud Function​ 代码中全局可用,无需 ​require ​语句即可使用。

重要 - 不要创建任何全局变量

Cloud Functions​ 不能有状态。 他们可以读取数据并将数据写入数据库,但您不能像下面的示例中那样创建全局变量。

let name = "Satoshi"; // NOT ALLOWED

Moralis.Cloud.define("functionName", async (request) => {
  let age = 20; // allowed
});

原因是您的云代码将在您的服务器的许多实例之间实现负载平衡,以便 Moralis 可以无限扩展您的应用程序。

注意:服务器的不同实例不会共享在函数体之外定义的任何变量。

您的服务器的所有实例将共享同一个数据库。 因此,如果您需要跨实例共享数据,建议您将其存储在数据库中。

如果你创建全局变量,参考下面例子的结果

let count = 0; // very bad

Moralis.Cloud.define("increment", async (request) => {
  logger.info(count);
  count++;
});

// If you call the function above you may get the following results
// 0
// 0
// 0
// 1
// 1
// 2
// 1
// 3
// 0
// 2

为什么increment功能无法正常工作?

因为每次请求被随机路由到服务器的不同实例并且每个实例都有自己单独的计数变量。

因此,我们希望您现在明白为什么不应该使用全局变量。但是全局常量可以使用,因为它们只会被复制到所有实例并且不会改变。

IDE 设置

您可以使用​moralis-admin-cli​ 在您喜欢的IDE 中编写您的云函数。

首先,您需要通过在终端中运行以下代码来安装它:

npm install -g moralis-admin-cli

安装 ​Moralis Admin CLI​ 后,您可以前往管理面板并在您要使用的服务器上打开云功能。

在模态框的下部,将有一个代码片段,您需要在终端中运行。

您唯一需要更改的是计算机上包含 ​Cloud Functions​ 的本地 JavaScript 文件夹的路径。

moralis-admin-cli watch-cloud-folder --moralisApiKey your_api_key --moralisApiSecret your_api_secret --moralisSubdomain subdomain.moralis.io --autoSave 1 --moralisCloudfolder /path/to/cloud/folder

运行命令后,每次保存都会在后端自动更新云代码!

调试

出于调试或信息目的,打印消息通常很有用。 为此目的可以获得一个记录器。 它会将消息打印到 Moralis 仪表板的“Logs > info”部分。

const logger = Moralis.Cloud.getLogger();
logger.info("Hello World"); 

在控制台中实时打印日志

moralis-admin-cli get-logs --moralisApiKey MORALIS_CLI_API_KEY --moralisApiSecret MORALIS_CLI_SECRET_KEY

调用云函数

你可以通过​JS​、​React​来实现

const params =  { movie: "The Matrix" };
const ratings = await Moralis.Cloud.run("averageStars", params);
// ratings should be 4.5
import { useMoralisCloudFunction } from "react-moralis";

function App() {
    const { fetch } = useMoralisCloudFunction(
        "averageStars",
        { movie: "The Moralis" },
        { autoFetch: false }
    );

    const cloudCall = () => {
        fetch({
            onSuccess: (data) => console.log(data), // ratings should be 4.5
        });
    };

    return (
        <button onClick={cloudCall}>Make Cloud Call</button>
    );
}

export default App;

通常,会传递两个参数给 Cloud Functions:

  1. request ​- 请求对象包含有关请求的信息。 设置了以下字段:
  2. params ​- 参数对象由客户端发送到函数。
  3. user ​- 发出请求的 ​Moralis.User​。 如果没有登录用户,则不会设置。

如果函数成功,客户端中的响应如下所示:

{ "result": 4.8 }

如果出现错误,客户端中的响应如下所示:

{
  "code": 141,
  "error": "movie lookup failed"
}

通过 REST API 调用

可以使用简单的 GET 请求直接调用 ​Cloud Functions​。 将 Moralis 应用程序 ID 和任何云函数参数作为查询参数添加到 Moralis 服务器 URL。 假设您有以下云功能:

Moralis.Cloud.define("Hello", (request) => {
return `Hello ${request.params.name}! Cloud functions are cool!`
});

然后 URL 将如下所示:

https://1a2b3c4c5d6f.moralis.io:2053/server/functions/Hello?_ApplicationId=1a2b3c4c5d6f1a2b3c4c5d6f&name=CryptoChad

URL 具有以下结构:

  • 完整的Morlis server地址
  • /functions/
  • Cloud Function名称
  • ?_ApplicationId=yourMoralisAppId
  • (可选) Cloud Function 参数键值对: &m1=value&m2=value.

云代码中的万能钥匙

在需要主密钥的请求中设置 ​useMasterKey:true​。

示例:

query.find({useMasterKey:true});
object.save(null,{useMasterKey:true});
Moralis.Object.saveAll(objects,{useMasterKey:true});

注意:当您使用 ​useMasterKey:true​ 时,默认情况下可以在云功能中访问主密钥

重要提示:不应在您的前端代码上使用主密钥,因为它可以在用户的浏览器中访问。

如果您使用来自您控制的另一个后端服务器的主密钥,您可以使用以下代码来初始化主密钥:

const Moralis = require("moralis/node"); // Node.js
const appId = "YOUR_MORALIS_APP_ID";
const serverUrl = "YOUR_MORALIS_SERVER_URL";
const masterKey = "YOUR_MORALIS_MASTER_KEY"; 

Moralis.start({ serverUrl, appId, masterKey });

云功能验证

确保提供 ​Cloud Function​ 所需的参数并采用必要的格式非常重要。 您可以指定将在您的云函数之前调用的验证器函数或对象。

让我们看一下averageStars的例子。 如果你想确保 ​request.params.movie​ 被提供,并且 averageStars 只能由登录用户调用,你可以在函数中添加一个验证器对象。

Moralis.Cloud.define("averageStars", async (request) => {
  const query = new Moralis.Query("Review");
  query.equalTo("movie", request.params.movie);
  const results = await query.find();
  let sum = 0;
  for (let i = 0; i < results.length; ++i) {
    sum += results[i].get("stars");
  }
  return sum / results.length;
},{
  fields : ['movie'],
  requireUser: true
});

如果不满足验证器对象中指定的规则,云函数将不会运行。 这意味着您可以自信地构建您的函数,知道 ​request.params.movie​ 以及 ​request.user​ 已定义。

高级云功能验证

通常,不仅定义 ​request.params.movie​ 很重要,而且它是正确的数据类型。 您可以通过为“验证器”中的字段参数提供一个对象来做到这一点。

Moralis.Cloud.define("averageStars", async (request) => {
  const query = new Moralis.Query("Review");
  query.equalTo("movie", request.params.movie);
  const results = await query.find();
  let sum = 0;
  for (let i = 0; i < results.length; ++i) {
    sum += results[i].get("stars");
  }
  return sum / results.length;
},{
  fields : {
    movie : {
      required: true,
      type: String,
      options: val => {
        return val.length < 20;
      },
      error: "Movie must be less than 20 characters"
    }
  },
  requireUserKeys: {
    accType : {
      options: 'reviewer',
      error: 'Only reviewers can get average stars'
    }
  }
});

此功能仅在以下情况下运行:

  • request.params.movie​ 已定义。
  • request.params.movie​ 是一个字符串。
  • request.params.movie​ 少于 20 个字符。
  • request.user​ 已定义。
  • request.user.get('accType')​ 已定义。
  • request.user.get('accType')​ 等于'reviewer'。

完整的内置验证选项包括:

  • requireMaster​:函数是否需要​masterKey​才能运行。
  • requireUser​:函数是否需要 ​request.user​ 才能运行。
  • validateMasterKey​:验证器是否应该在 ​masterKey ​上运行(默认为 ​false​)。
  • fields​:请求所需的字段的数组或对象。
  • requireUserKeys​:要在 ​request.user​ 上验证的字段数组。

.fields​ 上的全部内置验证选项包括:

  • type​:​request.params[field]​ 或 ​request.object.get(field)​ 的类型。
  • default​:如果该字段为空,则该字段应默认为什么。
  • required​:该字段是否为必填项。
  • options​:单个选项、选项数组或字段允许值的自定义函数。
  • constant​:字段是否不可变。
  • error​:验证失败时的自定义错误消息。

您还可以将函数传递给验证器。 这可以帮助您将重复出现的逻辑应用于您的云代码。

const validationRules = request => {
  if (request.master) {
    return;
  }
  if (!request.user || request.user.id !== 'masterUser') {
    throw 'Unauthorized';
  }
}

Moralis.Cloud.define('adminFunction', request => {
// do admin code here, confident that request.user.id is masterUser, or masterKey is provided
},validationRules)

Moralis.Cloud.define('adminFunctionTwo', request => {
// do admin code here, confident that request.user.id is masterUser, or masterKey is provided
},validationRules)

需要注意的一些注意事项

验证功能将在您的 ​Cloud Code Functions​ 之前运行。 您可以在此处使用 ​async ​和 ​Promise​,但尽量保持验证尽可能简单和快速,以便您的云请求快速解决。

如前所述,云验证器对象不会验证是否提供了主密钥,除非设置了 ​validateMasterKey:true​。 但是,如果您将验证器设置为函数,则该函数将始终运行。

单位

Moralis 单位在您的云函数中可用。

为了成功运行 units 函数,您始终需要指定一个方法和一个值

转换wei

将任何以太币值转换为 wei。

const result = await Moralis.Cloud.units({
  method: "toWei",
  value: 1
});
return result;

结果为: 1000000000000000000

将wei转换

将任何wei值转换为ether。

const result = Moralis.Cloud.units({
  method: "fromWei",
  value: 1000000000000000
});
return result;

结果为: 0.001

十六进制

将任何给定值转换为其十六进制表示。

 const result = Moralis.Cloud.units({
   method: "toHex",
   value: 100
 });
 return result;

结果为: 64

使用小数

使用自定义小数值转换任何给定值。

result = Moralis.Cloud.units({
  method: "fromWei",
  value: 10000000000000,
  decimals: 10
});
return result

结果为: 1000

Web3

Cloud Code 中提供了 Web3 功能,包括调用合约方法的能力。 Moralis 使用 Web3.js 和 ethers.js 库。

// get a web3 instance for a specific chain
const web3 = Moralis.web3ByChain("0x1"); // mainnet
    
// get an ethers instance for a specific chain and ethersjs library
const web3 = Moralis.ethersByChain("0x4"); // rinkeby

当您调用 ​Moralis.ethersByChain()​ 时,您将获得一个包含​provider​和 ​ethers ​库的对象。

{
  provider: //A provider with the supplied chainId,
  ethers: //ethers.js library,
}

通过为您希望连接的区块链提供 ​chainId ​来请求 web3 对象。

注意: ​Moralis.web3ByChain()​ 或 ​Moralis.ethersByChain()​ 返回的 web3 实例无法签署交易。 有一种使用私钥签署交易的方法,但出于安全原因不建议这样做。

以下是当前支持的链列表。

 Chain名称  ChainId
 Ethereum (Mainnet)  ​0x1
 Ropsten (Ethereum Testnet)  ​0x3
 Rinkeby  ​0x4
 Goerli (Ethereum Testnet)  ​0x5
 Kovan  ​0x2a
 Binance Smart Chain (Mainnet)  ​0x38
 Binance Smart Chain (Testnet)  ​0x61
 Matic (Mainnet)  ​0x89
 Mumbai (Matic Testnet)  ​0x13881
 Local Dev (Ganache, Hardhat)  ​0x539

合同

一旦你有了一个 web3 实例,你就可以通过使用 ​ABI ​和合约地址构造一个合约实例来使用它来进行合约调用。

const contract = new web3.eth.Contract(abi, address);

为方便起见,Moralis 为 ERC20、ERC721 和 ERC1155 捆绑了 Openzepplin ABI。

  • Moralis.Web3.abis.erc20
  • Moralis.Web3.abis.erc721
  • Moralis.Web3.abis.erc1155

把这一切放在一起...

const web3 = Moralis.web3ByChain("0x38"); // BSC
const abi = Moralis.Web3.abis.erc20;
const address = "0x...."

// create contract instance
const contract = new web3.eth.Contract(abi, address);

// get contract name
const name = await contract.methods
  .name()
  .call()
  .catch(() => "");

有关 Web3.js 合约接口的更多详细信息,请参阅 Web3.js 文档的 web3.eth.Contract 部分。

Moralis.web3ByChain()​ 返回的 Web3 实例无法签署交易。 在不久的将来,可以使用自定义插件来做到这一点。 目前,如果您需要进行链上合约交互,请考虑在前端执行它们或创建一个 NodeJS 后端,您可以在其中访问Truffle's HdWalletProvider

合同 ABI

要查找已在以太坊主网上发布的合约的 ​ABI​,您可以通过搜索合约地址并查看“合约”选项卡来查看 Etherscan。 发布 ABI 的其他链也有类似的区块浏览器。 例如,你可以去 BscScan 获取 ​Binance Smart Chain​。

对于你自己的合约,​ABI ​可以在编译合约后的 build 目录中找到

  • Truffle: ​/truffle/build/contracts/myContract.json
  • Hardhat: ​/artifacts/contracts/myContract.sol/myContract.json

如何使用自定义 RPC url 的示例

通过使用​ethers​:

const web3 = Moralis.ethersByChain("0x1")
var url = 'https://speedy-nodes-nyc.moralis.io/YOUR_ID_HERE/eth/mainnet';
var customHttpProvider = new web3.ethers.providers.JsonRpcProvider(url);
customHttpProvider.getBlockNumber().then((result) => {
    logger.info("Current block number: " + result);
});

通过使用web3:

Moralis.Cloud.define("run_contract_function_with_web3", async (request) => {

  web3 = new Moralis.Web3(new Moralis.Web3.providers.HttpProvider("https://speedy-nodes-nyc.moralis.io/YOUR_ID_HERE/bsc/mainnet"));
    const abi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]
    address = "0x2170Ed0880ac9A755fd29B2688956BD959F933F8"
    const contract = new web3.eth.Contract(abi, address)
    const name = await contract.methods
      .name()
      .call()
      .catch((e) => logger.error(`callName: ${e}${JSON.stringify(e, null, 2)}`))
    return name;    
});

IPFS

IPFS 功能在您的云功能中可用。 为了成功上传到 IPFS,您需要指定:

  • A source type:​sourceType
  • A source:​source

以下是 ​sourceType ​支持的值

url

将给定 URL 的内容推送到 IPFS。

const result = await Moralis.Cloud.toIpfs({
  sourceType: "url",
  source: "https://moralis.io/wp-content/uploads/2021/06/Moralis-Glass-Favicon.svg",
});
return result;

结果为:{ ​"path": "https://ipfs.moralis.io:2053/ipfs/QmYrUVhr1f6ZpZ4jrmi7mSv5X8MGjxxrgaERWde3cFASL6"​ }

string

将字符串推送到 IPFS。

 const result = await Moralis.Cloud.toIpfs({
   sourceType: "string",
   source: "Moralis rules <3",
 });
 return result;

结果为: { ​"path": "https://ipfs.moralis.io:2053/ipfs/QmeYY26fCN4t2Apo9Nqix5ZDhJwcjoyDVLLz85TLkoiqpn"​ }

object

将对象推送到 IPFS。

const result = await Moralis.Cloud.toIpfs({
  sourceType: 'object',
  source: {
    type: "Monster",
    lp: 100,
    spells: {
      base: ["spell one","spell two"],
      locked: ["spell three"],
    }
  },
});
return result;

结果为: { ​"path": "https://ipfs.moralis.io:2053/ipfs/QmWowsJ74rYCHUYhag83Puky1qZTSsdj3n7bT2ejE2NvCJ"​ }

Base64 Binary

将 base64 文件推送到 IPFS。

const result = await Moralis.Cloud.toIpfs({
  sourceType: "base64Binary",
  source: "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoGCBETExcTEhETGBcYFxkaGBkZGRkZG",
});
return result;

结果为: { ​"path": "https://ipfs.moralis.io:2053/ipfs/QmaZRSn8cHKUN5LsvuY6M8a5LzV76uFKeZ9khthPj2rHhw" ​}

Base64

将 base64 字符串推送到 IPFS。

const result = await Moralis.Cloud.toIpfs({
  sourceType: "base64",
  source: "TW9yYWxpcyBydWxlcyA8Mw=="
});
return result;

结果为: { ​"path": "https://ipfs.moralis.io:2053/ipfs/QmeYY26fCN4t2Apo9Nqix5ZDhJwcjoyDVLLz85TLkoiqpn"​ }