云代码指南

介绍

为了在服务端执行一些业务逻辑操作,你需要使用我们提供的Cloud Code功能,编写JavaScript代码,并部署到我们的平台上。通过Cloud Code,你可以拦截save请求,在save object之前或之后做一些事情。你也可以自定义业务函数,并通过SDK调用。你还可以调用部分第三方库来实现你的业务逻辑。甚至,你可以将整个网站架设在Cloud Code之上,我们提供了web hosting服务。详细介绍如下。

JavaScript指南

云代码可以完全运行所有JavaScript SDK提供的功能,但是不提供浏览器的localStorage存储。查看《JavaScript SDK开发指南》

安装命令行工具

推荐安装基于node.js的avoscloud命令行工具,通过该工具可以创建、部署、发布、回滚、查询云代码,详细请参考这篇博客

Cloud code 管理

首先,请进入App的云代码管理界面:

image

可以看到整个管理界面分为三个部分:

  • 代码库 用来设置项目的源码仓库信息,包括从这里可以下载Cloud Code项目的初始框架代码,拷贝用于私有git仓库的deploy key等。
  • 部署 用于部署Cloud Code到测试环境或者生产环境。
  • 日志 用于查看Cloud Code日志

下载基本项目框架

点击代码库菜单的下载项目框架(基本版)链接,会自动下载一个初始的项目框架,下载后的文件是一个zip打包文件,请解压该文件,会看到一个以App名称命名的目录,进入该目录会看到三个文件夹:

image

目录结构是这样:

-config/
  global.json
-cloud/
  main.js
-public/
  README

其中:

  • public目录,用于存放Web Hosting功能的静态资源文件,具体请看后面的介绍。
  • config目录下是项目的配置文件global.json,已经按照你的项目信息(主要是App id和App key)帮你自动配置好了。
  • cloud目录下有一个main.js,这就是你的业务逻辑代码存放的地方,初始内容定义了一个函数,代码如下:
// Use AV.Cloud.define to define as many cloud functions as you want.
// For example:
AV.Cloud.define("hello", function(request, response) {
  response.success("Hello world!");
});

这段代码定义了一个名为hello的函数,它简单的返回应答Hello world!

部署代码

我们可以直接将这个初步的项目框架部署到Cloud Code上尝试运行一下。

首先,你需要将这个项目提交到一个git仓库,AVOS Cloud并不提供源码的版本管理功能,而是借助于git这个优秀的分布式版本管理工具。我们推荐您使用CSDN Code平台github或者BitBucket这样第三方的源码 托管网站,也可以使用您自己搭建的git仓库(比如使用gitlab.org)。下面我们详细描述下怎么使用。

使用 CSDN Code 托管源码

CSDN CODE是国内非常优秀的源码托管平台,您可以使用CODE平台提供公有仓库和有限的私有仓库完成对代码的管理功能。

以下是使用CODE平台与AVOS Cloud云代码结合的一个例子。 首先在CODE上创建一个项目

image 请注意,在已经有项目代码的情况下,一般不推荐”使用README文件初始化项目”

接下来按照给出的提示,将源代码push到这个代码仓中

cd ${PROJECT_DIR}
git init
git add *
git commit -m "first commit"
git remote add origin git@code.csdn.net:${yourname}/test.git
git push -u origin master

我们已经将源码成功推送到CODE平台,接下来到AVOS Cloud云代码的管理界面填写下你的git地址(请注意,一定要填写以git@开头的地址,我们暂不支持https协议clone源码)并点击save按钮保存: image

添加deploy key到你的CODE平台项目上(deploy key是我们AVOS Cloud机器的ssh public key) 保存到”项目设置””项目公钥”中,创建新的一项avoscloud:

image

下一步,部署源码到测试环境,进入云代码-部署菜单,点击部署到开发环境的框里的按钮部署:

image 部署成功后,可以看到开发环境版本号从undeploy变成了当前提交的源码版本号

使用 GitHub 托管源码

使用BitBucket与此类似,恕不重复。

Github是一个非常优秀的源码托管平台,您可以使用它的免费帐号,那将无法创建私有仓库(bucket可以创建私有仓库),也可以付费成为高级用户,可以创建私有仓库。

首先在github上创建一个项目,比如就叫test:

image

image

接下来按照github给出的提示,我们将源码push到这个代码仓库:

cd ${PROJECT_DIR}
git init
git add *
git commit -m "first commit"
git remote add origin git@github.com:${yourname}/test.git
git push -u origin master

到这一步我们已经将源码成功push到github,接下来到Cloud Code的管理界面填写下你的git地址(请注意,一定要填写以git@开头的地址,我们暂不支持https协议clone源码)并点击save按钮保存:

image

并添加deploy key到你的github项目(deploy key是我们Cloud code机器的ssh public key),如果您是私有项目,需要设置deploy key,

拷贝代码库菜单里的deploy key:

image

保存到github setting里的deploy key,创建新的一项avoscloud:

image

下一步,部署源码到测试环境,进入云代码-部署菜单,点击部署到开发环境的框里的按钮部署

image

部署成功后,可以看到开发环境版本号undeploy变成了当前提交的源码版本号。

自动部署

为了安全考虑,我们去除了自动部署Git仓库的功能。

Gitlab 无法部署问题

很多用户自己使用Gitlab搭建了自己的源码仓库,有朋友会遇到无法部署到AVOSCloud的问题,即使设置了Deploy Key,却仍然要求输入密码。

可能的原因和解决办法如下:

  • 确保您gitlab运行所在服务器的/etc/shadow文件里的git(或者gitlab)用户一行的!修改为*,原因参考这里,并重启SSH服务sudo service ssh restart
  • 在拷贝deploy key时,确保没有多余的换行符号。
  • Gitlab目前不支持有comment的deploy key。早期AVOSCloud用户生成的deploy key可能带comment,这个comment是在deploy key的末尾76个字符长度的字符串,例如下面这个deploy key:
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EZmrZZjbKb07yipeSkL+Hm+9mZAqyMfPu6BTAib+RVy57jAP/lZXuosyPwtLolTwdyCXjuaDw9zNwHdweHfqOX0TlTQQSDBwsHL+ead/p6zBjn7VBL0YytyYIQDXbLUM5d1f+wUYwB+Cav6nM9PPdBckT9Nc1slVQ9ITBAqKZhNegUYehVRqxa+CtH7XjN7w7/UZ3oYAvqx3t6si5TuZObWoH/poRYJJ+GxTZFBY+BXaREWmFLbGW4O1jGW9olIZJ5/l9GkTgl7BCUWJE7kLK5m7+DYnkBrOiqMsyj+ChAm+o3gJZWr++AFZj/pToS6Vdwg1SD0FFjUTHPaxkUlNw== App dxzag3zdjuxbbfufuy58x1mvjq93udpblx7qoq0g27z51cx3's cloud code deploy key

其中最后76个字符

App dxzag3zdjuxbbfufuy58x1mvjq93udpblx7qoq0g27z51cx3's cloud code deploy key

就是comment,删除这段字符串后的deploy key(如果没有这个字样的comment无需删除)保存到gitlab即可正常使用:

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EZmrZZjbKb07yipeSkL+Hm+9mZAqyMfPu6BTAib+RVy57jAP/lZXuosyPwtLolTwdyCXjuaDw9zNwHdweHfqOX0TlTQQSDBwsHL+ead/p6zBjn7VBL0YytyYIQDXbLUM5d1f+wUYwB+Cav6nM9PPdBckT9Nc1slVQ9ITBAqKZhNegUYehVRqxa+CtH7XjN7w7/UZ3oYAvqx3t6si5TuZObWoH/poRYJJ+GxTZFBY+BXaREWmFLbGW4O1jGW9olIZJ5/l9GkTgl7BCUWJE7kLK5m7+DYnkBrOiqMsyj+ChAm+o3gJZWr++AFZj/pToS6Vdwg1SD0FFjUTHPaxkUlNw==

通过 API 调用函数

部署成功后,我们可以尝试调用刚才定义的hello函数:

curl -X POST -H "Content-Type: application/json; charset=utf-8"   \
       -H "X-AVOSCloud-Application-Id: {{appid}}"          \
       -H "X-AVOSCloud-Application-Key: {{appkey}}"        \
       -H "X-AVOSCloud-Application-Production: 0"  -d '{}' \
https://cn.avoscloud.com/1/functions/hello

返回结果:

{"result":"Hello world!"}

恭喜你!你已经成功部署了Cloud Code并运行了第一个函数。

接下来,你可以尝试修改hello函数的返回值,然后push到github仓库并部署,看看调用的结果是否也相应地作出改变。

在Android SDK里调用hello函数,参考Android SDK开发指南

在iOS SDK里调用云代码函数,参考iOS OSX SDK开发指南

本地调试云代码

请通过npm安装调试SDK:

sudo npm install -g avoscloud-code

如果从npm安装失败,可以从Github安装:

sudo npm install -g  git+https://github.com/avos/CloudCodeMockSDK

然后在云代码项目的根目录执行avoscloud命令,就可以启动本地模拟服务器。

  • 访问http://localhost:3000/即可访问到你的云主机代码,子路径按照你在app.js里配置的即可访问。
  • 访问http://localhost:3000/avos进入云代码函数和Class Hooks函数调试界面。
  • 测试函数:
curl -X POST -H 'Content-Type:application/json' \
    -d '{ "name": "dennis"}' \
    http://localhost:3000/avos/hello

其中hello是你通过AV.Cloud.define定义的函数名称。

  • 测试beforeSave,afterSave,afterUpdate,beforeDelete/afterDelete等:
curl -X POST -H 'Content-Type:application/json' \
     -d '{ "name": "dennis"}' \
   http://localhost:3000/avos/MyUser/beforeSave

其中MyUser是className,beforeSave指定调用MyUser定义的beforeSave函数,其他函数类似。

测试环境和生产环境

你应该注意到了,我们在调用REST API的时候设置了特殊的HTTP头X-AVOSCloud-Application-Production,这个头信息用于设置 调用的Cloud Code代码环境。

  • 0 表示调用开发环境的代码
  • 1 表示调用生产环境的代码

具体到SDK内的调用,请看各个平台的SDK指南。

我们尝试将``修改为1,然后再调用:

curl -X POST -H "Content-Type: application/json; charset=utf-8"   \
       -H "X-AVOSCloud-Application-Id: {{appid}}"          \
       -H "X-AVOSCloud-Application-Key: {{appkey}}"        \
       -H "X-AVOSCloud-Application-Production: 1"  -d '{}' \
https://cn.avoscloud.com/1/functions/hello

服务端返回告诉你production还没有部署:

{"code":1,"error":"The cloud code isn't deployed for prod 1."}

默认自动部署都是部署到开发环境。通过点击部署菜单下面的部署到生产环境框内的部署按钮,可以将 开发环境的当前版本的代码部署到生产环境:

image

这样就隔离开发和生产环境,代码在开发环境测试通过后,再部署到生产环境是更安全的做法。

Cloud 函数

让我们看一个明显更复杂的例子来展示Cloud Code的用途。在云端进行计算的一个重要理由是,你不需要将大量的数据发送到设备上做计算,而是将这些计算放到服务端,并返回结果这一点点信息就好。例如,假设你写了一个App,可以让用户对电影评分,一个评分对象大概是这样:

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

stars表示评分,1-5。如果你想查找《黑客帝国》这部电影的平均分,你可以找出这部电影的所有评分,并在设备上根据这个查询结果计算平均分。但是这样一来,尽管你只是需要平均分这样一个数字,却不得不耗费大量的带宽来传输所有的评分。通过Cloud Code,我们可以简单地传入电影名称,然后返回电影的平均分。

Cloud函数接收JSON格式的请求对象,我们可以用它来传入电影名称。整个AVCloud JavaScript SDK都在Cloud Code运行环境上有效,可以直接使用,所以我们可以使用它来查询所有的评分。结合一起,实现averageStars函数的代码如下:

AV.Cloud.define("averageStars", function(request, response) {
  var query = new AV.Query("Review");
  query.equalTo("movie", request.params.movie);
  query.find({
    success: function(results) {
      var sum = 0;
      for (var i = 0; i < results.length; ++i) {
        sum += results[i].get("stars");
      }
      response.success(sum / results.length);
    },
    error: function() {
      response.error("movie lookup failed");
    }
  });
});

averageStarshello函数的唯一区别是当我们调用函数的时候,我们必须提供参数给request.params.movie。继续读下去,我们将介绍如何调用Cloud函数。

调用一个函数

Cloud函数可以被各种客户端SDK调用,也可以通过REST API调用,例如,使用一部电影的名称去调用averageStars函数:

curl -X POST -H "Content-Type: application/json; charset=utf-8"   \
       -H "X-AVOSCloud-Application-Id: {{appid}}"          \
       -H "X-AVOSCloud-Application-Key: {{appkey}}"        \
       -H "X-AVOSCloud-Application-Production: 0"  -d '{}' \
       -d '{"movie":"The Matrix"}' \
https://cn.avoscloud.com/1/functions/averageStars

有两个参数会被传入到Cloud函数:

  • request - 包装了请求信息的请求对象,下列这些字段将被设置到request对象内:
    • params - 客户端发送的参数对象
    • user - AV.User对象,发起调用的用户,如果没有登录,则不会设置此对象。
  • response - 应答对象,包含两个函数:
    • success - 这个函数可以接收一个额外的参数,表示返回给客户端的结果数据。这个参数对象可以是任意的JSON对象或数组,并且可以包含AV.Object对象。
    • error - 如果这个方法被调用,则表示发生了一个错误。它也接收一个额外的参数来传递给客户端,提供有意义的错误信息。

如果函数调用成功,返回给客户端的结果类似这样:

{
  "result": 4.8
}

如果调用有错误,则返回:

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

在云代码里调用已定义的函数

使用AV.Cloud.run可以在云代码中调用AV.Cloud.define定义的云代码函数:

AV.Cloud.run("hello", {name: 'dennis'}, {
  success: function(data){
      //调用成功,得到成功的应答data
  },
  error: function(err){
      //处理调用失败
  }
});

API参数详解参见AV.Cloud.run

在 save 前修改对象

在某些情况下,你可能不想简单地丢弃无效的数据,而是想清理一下再保存。beforeSave可以帮你做到这一点,你只要调用response.success作用到修改后的对象上。

在我们电影评分的例子里,你可能想保证评论不要过长,太长的单个评论可能难以显示。我们可以使用beforeSave来截断评论到140个字符:

AV.Cloud.beforeSave("Review", function(request, response) {
  var comment = request.object.get("comment");
  if (comment.length > 140) {
    // 截断并添加...
    request.object.set("comment", comment.substring(0, 137) + "...");
  }
  response.success();
});

在 save 后执行动作

在另一些情况下,你可能想在保存对象后做一些动作,例如发送一条push通知。类似的,你可以通过afterSave函数做到。举个例子,你想跟踪一篇博客的评论总数字,你可以这样做:

AV.Cloud.afterSave("Comment", function(request) {
  query = new AV.Query("Post");
  query.get(request.object.get("post").id, {
    success: function(post) {
      post.increment("comments");
      post.save();
    },
    error: function(error) {
      throw "Got an error " + error.code + " : " + error.message;
    }
  });
});

如果afterSave函数调用失败,save请求仍然会返回成功应答给客户端。afterSave发生的任何错误,都将记录到Cloud Code日志里。

在 update 更新后执行动作

同样,除了保存对象之外,更新一个对象也是很常见的操作,我们允许你在更新对象后执行特定的动作,这是通过afterUpdate函数做到。比如每次修改文章后简单地记录日志:

AV.Cloud.afterUpdate("Article", function(request) {
   console.log("Updated article,the id is :" + request.object.id);
});

在 delete 前执行动作

很多时候,你希望在删除一个对象前做一些检查工作。比如你要删除一个相册(Album)前,会去检测这个相册里的图片(Photo)是不是已经都被删除了,这都可以通过beforeDelete函数来定义一个钩子(callback)函数来做这些检查,示例代码:

AV.Cloud.beforeDelete("Album", function(request, response) {
  //查询Photot中还有没有属于这个相册的照片
  query = new AV.Query("Photo");
  var album = AV.Object.createWithoutData('Album', request.object.id);
  query.equalTo("album", album);
  query.count({
    success: function(count) {
      if (count > 0) {
        //还有照片,不能删除,调用error方法
        response.error("Can't delete album if it still has photos.");
      } else {
        //没有照片,可以删除,调用success方法
        response.success();
      }
    },
    error: function(error) {
      response.error("Error " + error.code + " : " + error.message + " when getting photo count.");
    }
  });
});

在 delete 后执行动作

另一些情况下,你可能希望在一个对象被删除后执行操作,例如递减计数、删除关联对象等。同样以相册为例,这次我们不在beforeDelete中检查是否相册中还有照片,而是在相册删除后,同时删除相册中的照片,这是通过afterDelete函数来实现:

AV.Cloud.afterDelete("Album", function(request) {
  query = new AV.Query("Photo");
  var album = AV.Object.createWithoutData('Album', request.object.id);
  query.equalTo("album", album);
  query.find({
    success: function(posts) {
    //查询本相册的照片,遍历删除
    AV.Object.destroyAll(posts);
    },
    error: function(error) {
      console.error("Error finding related comments " + error.code + ": " + error.message);
    }
  });
});

定时任务

很多时候可能你想做一些定期任务,比如半夜清理过期数据,或者每周一给所有用户发送推送消息等等,我们提供了定时任务给您,让您可以在云代码中运行这样的任务。

我们提供的定时任务的最小时间单位是秒,正常情况下我们都能将误差控制在秒级别。

原来提供的AV.Cloud.setIntervalAV.Cloud.cronjob都已经废弃,这两个函数的功能变成和AV.Cloud.define一样,已经定义的任务会自动帮您做转换并启动

定时任务也是普通的AV.Cloud.define定义的云代码函数,比如我们定义一个打印循环打印日志的任务log_timer

AV.Cloud.define("log_timer", function(req, res){
    console.log("Log in timer.");
    return res.success();
});

部署云代码之后,进入云代码管理菜单,左侧有个定时任务菜单:

image

选择创建定时器,选择定时任务执行的函数名称,执行环境等等:

image

定时任务分为两类:

  • 使用标准的crontab语法调度
  • 简单的循环调度,我们这里以循环调度为例,每隔5分钟打印日志

创建后,定时任务还没有启动,您需要在定时任务列表里启动这个任务:

image

接下里就可以在云代码日志里看到这条日志的打印:

image

我们再尝试定义一个复杂一点的任务,比如每周一早上8点准时发送推送消息给用户:

AV.Cloud.define("push_timer", function(req, res){
  AV.Push.send({
        channels: [ "Public" ],
        data: {
            alert: "Public message"
        }
    });
   return res.success();
});

创建定时器的时候,选择cron表达式并填写为0 0 8 ? * MON

crontab的基本语法是

秒  分钟 小时 每个月的日期(Day-of-Month)月份 星期(Day-of-Week) 年(可选)

一些常见的例子如下:

  • "0 0/5 * ?" 每隔5分钟执行一次
  • "10 0/5 * ?" 每隔5分钟执行一次,每次执行都在分钟开始的10秒,例如10:00:10,然后10:05:10等等。
  • "0 30 10-13 ? * WED,FRI" 每周三和每周五的10:30, 11:30, 12:30和13:30执行。
  • "0 0/30 8-9 5,20 * ?" 每个月的5号和20号的8点和10点之间每隔30分钟执行一次,也就是8:00, 8:30, 9:00和9:30。

资源限制

权限说明

云代码拥有超级权限,默认使用 master key 调用所有 API,因此会忽略 ACL 和 Class Permission 限制。

如果在你的 node.js 环境里也想做到超级权限,请调用下列代码初始化 SDK:

AV._initialize("app id", "app key", "master key");
AV.Cloud.useMasterKey();

定时器数量

开发环境和测试环境的定时器数量都限制在3个以内,也就是说你总共最多可以创建6个定时器。

超时

Cloud函数如果超过15秒没有返回,将被强制停止。beforeSaveafterSave函数的超时时间限制在3秒内。如果beforeSaveafterSave函数被其他的Cloud函数调用,那么它们的超时时间会进一步被其他Cloud函数调用的剩余时间限制。例如,如果一个beforeSave函数 是被一个已经运行了13秒的Cloud函数触发,那么beforeSave函数就只剩下2秒的时间来运行,而正常情况下是3秒的限制。

Web Hosting的动态请求超时也被限定为15秒。

网络请求

successerror方法调用后,仍然在运行的网络请求将被取消掉。通常来说,你应该等待所有的网络请求完成,再调用success。对于afterSave函数来说,它并不需要调用success或者error,因此Cloud Code会等待所有的网络请求调用结束。

日志

云代码->日志,可以查看Cloud Code的部署和运行日志,还可以选择查看的日志级别:

image

如果你想打印日志到里面查看,可以使用console.log,console.error或者console.warn函数。console.errorconsole.warn都将写入error级别的日志。

AV.Cloud.define("Logger", function(request, response) {
  console.log(request.params);
  response.success();
});

Web Hosting

很多时候,除了运行在移动设备的App之外,您通常也会为App架设一个网站,可能只是简单地展现App的信息并提供AppStore或者Play商店下载链接,或者展示当前热门的用户等等。您也可能建设一个后台管理系统,用来管理用户或者业务数据。 这一切都需要您去创建一个web应用,并且从VPS厂商那里购买一个虚拟主机来运行web应用,您可能还需要去购买一个域名。

不过现在,Cloud code为您提供了web hosting功能,可以让你设置一个App的二级域名xxx.avosapps.com,并部署您的web应用到该域名之下运行。同时支持静态资源和动态请求服务。

设置域名

首先,你需要到应用设置菜单里找到web主机子菜单,在这里填写您的域名:

image

上面将App的二级域名设置为myapp,设置之后,您应该可以马上访问http://myapp.avosapps.com(可能因为DNS生效延迟暂时不可访问,请耐心等待或者尝试刷新DNS缓存),如果还没有部署,您看到的应该是一个404页面。

绑定独立域名

如果你想为您的App绑定一个独立域名,需要您用注册的邮箱发送下列信息到我们的支持邮箱support@avoscloud.com提出申请或者从帮助菜单里的技术支持系统提出 Ticket:

  • 您想要绑定的域名(必须是您名下的域名,并且您也已经将CNAME或者A记录指向了avosapps.com)
  • 您的注册邮箱(必须与发送者的邮箱一致)
  • 您想要绑定的App Id(该应用必须位于注册邮箱的用户名下)
  • 您的域名的备案信息 (必须可以在工信部查询到)

我们将在3个工作日内审核,如果审核通过将为您绑定域名。

下载Web Hosting项目框架

进入云代码菜单,从代码库菜单下载项目框架(web主机版):

image

下载后的代码结构类似Cloud code(基本版),只是在Cloud目录下多了app.js文件和views目录:

-config/
  global.json
-cloud/
  main.js
  app.js
  -views/
    hello.ejs
-public/

并且cloud/main.js里还多了一行代码:

require('cloud/app.js');

用来加载app.js

代码部署的过程跟Cloud code部署是一样的,具体见上面的章节

静态资源

public目录下的资源将作为静态文件服务,例如,你在public下有个文件叫index.html,那么就可以通过http://${your_app_domain}.avosapps.com/index.html访问到这个文件。

通常,你会将资源文件按照类型分目录存放,比如css文件放在stylesheets目录下,将图片放在images目录下,将javascript文件放在js目录下,Cloud code同样能支持这些目录的访问。

例如,public/stylesheets/app.css可以通过http://${your_app_domain}.avosapps.com/stylesheets/app.css访问到。

在你的HTML文件里引用这些资源文件,使用相对路径即可,比如在public/index.html下引用app.css

<link href="stylesheets/app.css" rel="stylesheet" type="text/css" />

默认静态资源的Cache-Controlmax-age=0,这样在每次请求静态资源的时候都会去服务端查询是否更新,如果没有更新返回304状态码。你还可以在app.listen的时候传入选项,设置静态资源的maxAge:

//设置7天不过期
app.listen({"static": {maxAge: 604800000}});

请注意maxAge的单位是毫秒,这样cache-control头会变成max-age=604800。更多static选项参考static middleware

动态请求

如果只是展现静态资源,您可能使用Github Pages类似的免费服务也能做到,但是AVOSCloud提供的Web Hosting功能同时支持动态请求。这是通过编写Node.js代码,基于express.js这个web MVC框架做到的。

关于express.js框架,请参考官方文档来学习。

在下载的项目框架cloud/app.js,我们可以看到一个初始代码:

// 在Cloud code里初始化express框架
var express = require('express');
var app = express();
var name = require('cloud/name.js');

// App全局配置
app.set('views','cloud/views');   //设置模板目录
app.set('view engine', 'ejs');    // 设置template引擎
app.use(express.bodyParser());    // 读取请求body的中间件

//使用express路由API服务/hello的http GET请求
app.get('/hello', function(req, res) {
  res.render('hello', { message: 'Congrats, you just set up your app!' });
});
//最后,必须有这行代码来使express响应http请求
app.listen();

我们使用ejs模板来渲染view,默认的模板都放在views目录下,比如这里hello.ejs:

<%= message %>

简单地显示message内容。你还可以选用jade这个模板引擎:

app.set('view engine', 'jade');

您可以参照上面的部署文档来部署这个框架代码,部署成功之后,直接可以访问http://${your_app_domain}.avosapps.com/hello将看到展示的message:

Congrats, you just set up your app!

更多复杂的路由和参数传递,请看express.js框架文档

我们还提供了一个在线demo:http://myapp.avosapps.com/,源码在https://github.com/killme2008/cloudcode-test,您可以作为参考。

自定义404页面

自定义404页面在云代码里比较特殊,假设我们要渲染一个404页面,必须将下列代码放在app.listen()之后:

//在app.listen();之后。
app.use(function(req, res, next){
    res.status(404).render('404', {title: "Sorry, page not found"});
});

这将渲染views下面的404模板页面。

上传文件

在Cloud Code里上传文件也很容易,首先配置app使用bodyParser中间件,它会将上传表单里的文件存放到临时目录并构造一个文件对象放到request.files里:

app.use(express.bodyParser());

使用表单上传文件,假设文件字段名叫iconImage:

<form  enctype="multipart/form-data"
     method="post" action="/upload">
  <input type="file" name="iconImage"/>
  <input type="submit" name="submit" value="submit" />
</form>

上传文件使用multipart表单,并POST提交到/upload路径下。

接下来定义文件上传的处理函数,使用受到严格限制并且只能读取上传文件的fs模块:

var fs = require('fs');
app.post('/upload', function(req, res){
  var iconFile = req.files.iconImage;
  if(iconFile){
    fs.readFile(iconFile.path, function(err, data){
      if(err)
        return res.send("读取文件失败");
      var base64Data = data.toString('base64');
      var theFile = new AV.File(iconFile.name, {base64: base64Data});
      theFile.save().then(function(theFile){
        res.send("上传成功!");
      });
    });
  }else
    res.send("请选择一个文件。");
});

上传成功后,即可在数据管理平台里看到您所上传的文件。

处理用户登录和登出

假设你创建了一个支持web主机功能的云代码项目,在app.js里添加下列代码:

var express = require('express');
var app = express();
var avosExpressCookieSession = require('avos-express-cookie-session');

// App全局配置
app.set('views','cloud/views');   //设置模板目录
app.set('view engine', 'ejs');    // 设置template引擎
app.use(express.bodyParser());    // 读取请求body的中间件

//启用cookie
app.use(express.cookieParser('Your Cookie Secure'));
//使用avos-express-cookie-session记录登录信息到cookie。
app.use(avosExpressCookieSession({ cookie: { maxAge: 3600000 }}));

使用express.cookieParser中间件启用cookie,注意传入一个secret用于cookie加密(必须)。然后使用require('avos-express-cookie-session')导入的avosExpressCookieSession创建一个session存储,它会自动将AV.User的登录信息记录到cookie里,用户每次访问会自动检查用户是否已经登录,如果已经登录,可以通过AV.User.current()获取当前登录用户。

avos-express-cookie-session支持的选项包括:

  • cookie -- 可选参数,设置cookie属性,例如maxAge,secure等。我们会强制将httpOnly和signed设置为true。
  • fetchUser -- 是否自动fetch当前登录的AV.User对象。默认为false。如果设置为true,每个HTTP请求都将发起一次AVOS Cloud API调用来fetch用户对象。如果设置为false,默认只可以访问AV.User.current()当前用户的id属性,您可以在必要的时候fetch整个用户。通常保持默认的false就可以。
  • key -- session在cookie中存储的key名称,默认为avos.sess。

登录很简单:

app.get('/login', function(req, res) {
    // 渲染登录页面
    res.render('login.ejs');
});
// 点击登录页面的提交将出发下列函数
app.post('/login', function(req, res) {
    AV.User.logIn(req.body.username, req.body.password).then(function() {
      //登录成功,avosExpressCookieSession会自动将登录用户信息存储到cookie
      //跳转到profile页面。
      console.log('signin successfully: %j', AV.User.current());
      res.redirect('/profile');
    },function(error) {
      //登录失败,跳转到登录页面
      res.redirect('/login');
  });
});
//查看用户profile信息
app.get('/profile', function(req, res) {
    // 判断用户是否已经登录
    if (AV.User.current()) {
      // 如果已经登录,发送当前登录用户信息。
      res.send(AV.User.current());
    } else {
      // 没有登录,跳转到登录页面。
      res.redirect('/login');
    }
});

//调用此url来登出帐号
app.get('/logout', function(req, res) {
  //avosExpressCookieSession将自动清除登录cookie信息
    AV.User.logOut();
    res.redirect('/profile');
});

登录页面大概是这样login.ejs:

<html>
    <head></head>
    <body>
      <form method="post" action="/login">
        <label>Username</label>
        <input name="username"></input>
        <label>Password</label>
        <input name="password" type="password"></input>
        <input class="button" type="submit" value="登录">
      </form>
    </body>
  </html>

注意: express框架的express.session.MemoryStore在我们云代码中是无法正常工作的,因为我们的云代码是多主机,多进程运行,因此内存型session是无法共享的,建议用cookieSession中间件

启用HTTPS

为了安全性,我们可能会为网站加上HTTPS加密传输。我们的云代码支持网站托管,同样会有这样的需求。

因此我们在云代码中提供了一个新的middleware来强制让你的{domain}.avosapps.com的网站通过https访问,你只要这样:

var avosExpressHttpsRedirect = require('avos-express-https-redirect');
app.use(avosExpressHttpsRedirect());

部署并发布到生产环境之后,访问您的云代码网站二级域名都会强制通过HTTPS访问。测试环境的域名仍然不会启用HTTPS。

测试环境和开发环境

前面已经谈到Cloud Code的测试和生产环境之间的区别,可以通过HTTP头部X-AVOSCloud-Application-Production来区分。但是对于Web Hosting就没有办法通过这个HTTP头来方便的区分。

因此,我们其实为每个App创建了两个域名,除了xxx.avosapps.com之外,每个App还有dev.xxx.avosapps.com域名作为测试环境的域名。

部署的测试代码将运行在这个域名之上,在测试通过之后,通过部署菜单里的部署到生产环境按钮切换之后,可以在xxx.avosapps.com看到最新的运行结果。

注意:dev.xxx.avosapps.com的view会同时渲染到生产环境,app.js的逻辑代码会自动隔离。因此建议测试环境和生产环境的views目录区分开,并通过全局变量__production来判断当前环境是生产环境还是测试环境,分别设置views目录

if(__production)
  app.set('views', 'cloud/views');
else
  app.set('views', 'cloud/dev_views');

这样,测试代码将使用cloud/dev_views目录作为views模板目录。

HTTP 客户端

Cloud Code允许你使用AV.Cloud.httpRequest函数来发送HTTP请求到任意的HTTP服务器。这个函数接受一个选项对象来配置请求,一个简单的GET请求看起来是这样:

AV.Cloud.httpRequest({
  url: 'http://www.google.com/',
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

当返回的HTTP状态码是成功的状态码(例如200,201等),则success函数会被调用,反之,则error函数将被调用。

查询参数

如果你想添加查询参数到URL末尾,你可以设置选项对象的params属性。你既可以传入一个JSON格式的key-value对象,像这样:

AV.Cloud.httpRequest({
  url: 'http://www.google.com/search',
  params: {
    q : 'Sean Plott'
  },
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

也可以是一个原始的字符串:

AV.Cloud.httpRequest({
  url: 'http://www.google.com/search',
  params: 'q=Sean Plott',
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

设置 HTTP 头部

通过设置选项对象的header属性,你可以发送HTTP头信息。假设你想设定请求的Content-Type,你可以这样做:

AV.Cloud.httpRequest({
  url: 'http://www.example.com/',
  headers: {
    'Content-Type': 'application/json'
  },
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

设置超时

默认请求超时设置为10秒,超过这个时间没有返回的请求将被强制终止,您可以调整这个超时,通过timeout选项:

AV.Cloud.httpRequest({
  url: 'http://www.example.com/',
  timeout: 15000,
  headers: {
    'Content-Type': 'application/json'
  },
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

上面的代码设置请求超时为15秒。

发送 POST 请求

通过设置选项对象的method属性就可以发送POST请求。同时可以设置选项对象的body属性来发送数据,一个简单的例子:

AV.Cloud.httpRequest({
  method: 'POST',
  url: 'http://www.example.com/create_post',
  body: {
    title: 'Vote for Pedro',
    body: 'If you vote for Pedro, your wildest dreams will come true'
  },
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

这将会发送一个POST请求到http://www.example.com/create_post,body是被URL编码过的表单数据。 如果你想使用JSON编码body,可以这样做:

AV.Cloud.httpRequest({
  method: 'POST',
  url: 'http://www.example.com/create_post',
  headers: {
    'Content-Type': 'application/json'
  },
  body: {
    title: 'Vote for Pedro',
    body: 'If you vote for Pedro, your wildest dreams will come true'
  },
  success: function(httpResponse) {
    console.log(httpResponse.text);
  },
  error: function(httpResponse) {
    console.error('Request failed with response code ' + httpResponse.status);
  }
});

当然,body可以被任何想发送出去的String对象替换。

HTTP 应答对象

传给success和error函数的应答对象包括下列属性:

  • status - HTTP状态码
  • headers - HTTP应答头部信息
  • text - 原始的应答body内容。
  • buffer - 原始的应答Buffer对象
  • data - 解析后的应答内容,如果Cloud Code可以解析返回的Content-Type的话(例如JSON格式,就可以被解析为一个JSON对象)

如果你不想要text(会消耗资源做字符串拼接),只需要buffer,那么可以设置请求的text选项为false:

AV.Cloud.httpRequest({
  method: 'POST',
  url: 'http://www.example.com/create_post',
  text: false,
  ......
});

模块

Cloud Code支持将JavaScript代码拆分成各个模块。为了避免加载模块带来的不必要的副作用,Cloud Code模块的运作方式和CommonJS模块类似。当一个模块被加载的时候,JavaScript文件首先被加载,然后执行文件内的源码,并返回全局的export对象。例如,假设cloud/name.js包含以下源码:

var coolNames = ['Ralph', 'Skippy', 'Chip', 'Ned', 'Scooter'];
exports.isACoolName = function(name) {
  return coolNames.indexOf(name) !== -1;
}

然后在cloud/main.js包含下列代码片段:

var name = require('cloud/name.js');
name.isACoolName('Fred'); // 返回false
name.isACoolName('Skippy'); // 返回true;
name.coolNames; // 未定义.

(提示,你可以利用console.log来打印这几个调用的返回值到日志)

name模块包含一个名为isACoolName的函数。require接收的路径是相对于你的Cloud Code项目的根路径,并且只限cloud/目录下的模块可以被加载。

可用的第三方模块

因为Cloud Code运行在沙箱环境,我们只允许使用部分类库,这个名单如下:

qiniu
underscore
underscore.string
moment
util
express
crypto
url
events
string_decoder
buffer
punycode
querystring
express-ejs-layouts
weibo
node-qiniu
mailgun
mandrill
stripe
sendgrid
xml2js

上面这些模块都可以直接require使用。 我们还提供受限制的fs文件模块,仅可以读取上传文件目录下的文件。