为了在服务端执行一些业务逻辑操作,你需要使用我们提供的Cloud Code功能,编写JavaScript代码,并部署到我们的平台上。通过Cloud Code,你可以拦截save请求,在save object之前或之后做一些事情。你也可以自定义业务函数,并通过SDK调用。你还可以调用部分第三方库来实现你的业务逻辑。甚至,你可以将整个网站架设在Cloud Code之上,我们提供了web hosting服务。详细介绍如下。
2014 年 8 月 14 号,Cloud Code 推出 2.0 版本,最主要特性:可以自由添加和使用三方类库,去除一些对模块的限制。从 14 号开始,新创建的应用都将使用云代码2.0版本。
- 时区问题:2.0版彻底修复了时区问题,应用不再需要自己对时间做 8 小时的时区修正。所以需要确认,在迁移到云代码2.0之前,移除代码中之前对时间修正的部分代码。
- 引入 package.json (可选):如果项目需要引入其他三方类库,可以像标准 node.js 项目一样,在项目根目录添加一个
package.json
配置文件,下面是一个简单的样例:
{
"name": "cloud-code-test",
"description": "Cloud Code test project.",
"version": "0.0.1",
"private": true,
"dependencies": {
"express": "3.x",
"async": "0.9.x"
}
}
需要注意的是,某些必需类库目前还是以 cloud-code 运行环境版本优先,具体版本信息:
nodejs: "0.10.29"
qiniu: "6.1.3"
underscore: "1.6.0"
underscore.string: "2.3.3"
moment: "2.7.0"
express: "3.4.0"
express-ejs-layouts: "0.3.1"
weibo: "0.6.9"
node-qiniu: "6.1.6"
mailgun: "0.4.2"
mandrill: "0.1.0"
stripe: "2.5.0"
sendgrid: "1.0.5"
xml2js: "0.4.4"
在以上问题都确认后,就可以进行升级动作。升级操作完成后,因为缓存的原因,需要等待最多5分钟,平台将自动迁移完成,在5分钟迁移时间内,老的云代码将继续提供服务,因此无需担心迁移期间服务暂停。
- 有着更好的资源隔离机制,因此
fs
等官方模块的限制取消了。 - 可以自由添加和使用三方类库
- 时区问题彻底解决
views
目录不再需要分成两个目录(cloud/views
和cloud/dev_views
)- 修正:项目从代码仓库迁出有可能失败的问题
云代码可以完全运行所有JavaScript SDK提供的功能,但是不提供浏览器的localStorage存储。查看《JavaScript SDK开发指南》。
推荐安装基于 node.js 的avoscloud
命令行工具,通过该工具可以创建、部署、发布、回滚、查询云代码,并且支持多应用管理。
详细请查看 云代码命令行工具详解
首先,请进入App的云代码管理界面:
可以看到整个管理界面分为三个部分:
- 代码库 用来设置项目的源码仓库信息,包括从这里可以下载Cloud Code项目的初始框架代码,拷贝用于私有git仓库的deploy key等。
- 部署 用于部署Cloud Code到测试环境或者生产环境。
- 日志 用于查看Cloud Code日志
点击代码库
菜单的下载项目框架(基本版)
链接,会自动下载一个初始的项目框架,下载后的文件是一个zip打包文件,请解压该文件,会看到一个以App名称命名的目录,进入该目录会看到三个文件夹:
目录结构是这样:
-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是国内非常优秀的源码托管平台,您可以使用CODE平台提供公有仓库和有限的私有仓库完成对代码的管理功能。
以下是使用CODE平台与AVOS Cloud云代码结合的一个例子。 首先在CODE上创建一个项目
请注意,在已经有项目代码的情况下,一般不推荐”使用README文件初始化项目”
接下来按照给出的提示,将源代码push到这个代码仓中
cd ${PROJECT_DIR}
git init
git add *
git commit -m "first commit"
git remote add origin [email protected]:${yourname}/test.git
git push -u origin master
我们已经将源码成功推送到CODE平台,接下来到AVOS Cloud云代码的管理界面填写下你的git地址(请注意,一定要填写以git@
开头的地址,我们暂不支持https协议clone源码)并点击save按钮保存:
添加deploy key到你的CODE平台项目上(deploy key是我们AVOS Cloud机器的ssh public key) 保存到”项目设置””项目公钥”中,创建新的一项avoscloud:
下一步,部署源码到测试环境,进入云代码-部署菜单,点击部署到开发环境的框里的按钮部署:
部署成功后,可以看到开发环境版本号从undeploy变成了当前提交的源码版本号
使用BitBucket与此类似,恕不重复。
Github是一个非常优秀的源码托管平台,您可以使用它的免费帐号,那将无法创建私有仓库(bucket可以创建私有仓库),也可以付费成为高级用户,可以创建私有仓库。
首先在github上创建一个项目,比如就叫test
:
接下来按照github给出的提示,我们将源码push到这个代码仓库:
cd ${PROJECT_DIR}
git init
git add *
git commit -m "first commit"
git remote add origin [email protected]:${yourname}/test.git
git push -u origin master
到这一步我们已经将源码成功push到github,接下来到Cloud Code的管理界面填写下你的git地址(请注意,一定要填写以git@
开头的地址,我们暂不支持https协议clone源码)并点击save按钮保存:
并添加deploy key到你的github项目(deploy key是我们Cloud code机器的ssh public key),如果您是私有项目,需要设置deploy key,
拷贝代码库
菜单里的deploy key
:
保存到github setting里的deploy key,创建新的一项avoscloud:
下一步,部署源码到测试环境,进入云代码-部署
菜单,点击部署到开发环境
的框里的按钮部署
:
部署成功后,可以看到开发环境版本号
从undeploy
变成了当前提交的源码版本号。
为了安全考虑,我们去除了自动部署Git仓库的功能。
很多用户自己使用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==
部署成功后,我们可以尝试调用刚才定义的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.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.1/functions/hello
服务端返回告诉你production还没有部署:
{"code":1,"error":"The cloud code isn't deployed for prod 1."}
默认自动部署都是部署到开发环境。通过点击部署
菜单下面的部署到生产环境
框内的部署
按钮,可以将
开发环境的当前版本的代码部署到生产环境:
这样就隔离开发和生产环境,代码在开发环境测试通过后,再部署到生产环境是更安全的做法。
让我们看一个明显更复杂的例子来展示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");
}
});
});
averageStars
和hello
函数的唯一区别是当我们调用函数的时候,我们必须提供参数给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.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。
在某些情况下,你可能不想简单地丢弃无效的数据,而是想清理一下再保存。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();
});
在另一些情况下,你可能想在保存对象后做一些动作,例如发送一条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日志里。
同样,除了保存对象之外,更新一个对象也是很常见的操作,我们允许你在更新对象后执行特定的动作,这是通过afterUpdate
函数做到。比如每次修改文章后简单地记录日志:
AV.Cloud.afterUpdate("Article", function(request) {
console.log("Updated article,the id is :" + request.object.id);
});
很多时候,你希望在删除一个对象前做一些检查工作。比如你要删除一个相册(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.");
}
});
});
另一些情况下,你可能希望在一个对象被删除后执行操作,例如递减计数、删除关联对象等。同样以相册为例,这次我们不在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.onVerified
函数:
AV.Cloud.onVerified('sms', function(request, response) {
console.log("onVerified: sms, user: " + request.object);
response.success();
函数的第一个参数是验证类型:短信验证为sms
,邮箱验证为email
。另外,数据库中相关的验证字段,如emailVerified
不需要修改,我们已经为你更新完成。
很多时候可能你想做一些定期任务,比如半夜清理过期数据,或者每周一给所有用户发送推送消息等等,我们提供了定时任务给您,让您可以在云代码中运行这样的任务。
我们提供的定时任务的最小时间单位是秒,正常情况下我们都能将误差控制在秒级别。
原来提供的AV.Cloud.setInterval
和AV.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();
});
部署云代码之后,进入云代码管理菜单,左侧有个定时任务菜单:
选择创建定时器,选择定时任务执行的函数名称,执行环境等等:
定时任务分为两类:
- 使用标准的crontab语法调度
- 简单的循环调度,我们这里以循环调度为例,每隔5分钟打印日志
创建后,定时任务还没有启动,您需要在定时任务列表里启动这个任务:
接下里就可以在云代码日志里看到这条日志的打印:
我们再尝试定义一个复杂一点的任务,比如每周一早上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秒没有返回,将被强制停止。beforeSave
和afterSave
函数的超时时间限制在3秒内。如果beforeSave
和afterSave
函数被其他的Cloud函数调用,那么它们的超时时间会进一步被其他Cloud函数调用的剩余时间限制。例如,如果一个beforeSave
函数
是被一个已经运行了13秒的Cloud函数触发,那么beforeSave
函数就只剩下2秒的时间来运行,而正常情况下是3秒的限制。
Web Hosting的动态请求超时也被限定为15秒。
当success
和error
方法调用后,仍然在运行的网络请求将被取消掉。通常来说,你应该等待所有的网络请求完成,再调用success
。对于afterSave
函数来说,它并不需要调用success
或者error
,因此Cloud Code会等待所有的网络请求调用结束。
云代码->日志,可以查看Cloud Code的部署和运行日志,还可以选择查看的日志级别:
如果你想打印日志到里面查看,可以使用console.log
,console.error
或者console.warn
函数。console.error
和console.warn
都将写入error级别的日志。
AV.Cloud.define("Logger", function(request, response) {
console.log(request.params);
response.success();
});
很多时候,除了运行在移动设备的App之外,您通常也会为App架设一个网站,可能只是简单地展现App的信息并提供AppStore或者Play商店下载链接,或者展示当前热门的用户等等。您也可能建设一个后台管理系统,用来管理用户或者业务数据。 这一切都需要您去创建一个web应用,并且从VPS厂商那里购买一个虚拟主机来运行web应用,您可能还需要去购买一个域名。
不过现在,Cloud code为您提供了web hosting功能,可以让你设置一个App的二级域名xxx.avosapps.com
,并部署您的web应用到该域名之下运行。同时支持静态资源和动态请求服务。
首先,你需要到应用设置
菜单里找到web主机
子菜单,在这里填写您的域名:
上面将App的二级域名设置为myapp
,设置之后,您应该可以马上访问http://myapp.avosapps.com
(可能因为DNS生效延迟暂时不可访问,请耐心等待或者尝试刷新DNS缓存),如果还没有部署,您看到的应该是一个404页面。
如果你想为您的App绑定一个独立域名,需要您用注册的邮箱发送下列信息到我们的支持邮箱[email protected]
提出申请或者从帮助菜单
里的技术支持系统提出 Ticket:
- 您想要绑定的域名(必须是您名下的域名,并且您也已经将CNAME或者A记录指向了avosapps.com)
- 您的注册邮箱(必须与发送者的邮箱一致)
- 您想要绑定的App Id(该应用必须位于注册邮箱的用户名下)
- 您的域名的备案信息 (必须可以在工信部查询到)
我们将在3个工作日内审核,如果审核通过将为您绑定域名。
进入云代码
菜单,从代码库
菜单下载项目框架(web主机版):
下载后的代码结构类似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-Control
是max-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页面,必须将下列代码放在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加密传输。我们的云代码支持网站托管,同样会有这样的需求。
因此我们在云代码中提供了一个新的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模板目录。
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);
}
});
通过设置选项对象的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秒。
通过设置选项对象的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对象替换。
传给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 1.0运行在沙箱环境,我们只允许使用部分类库,这个名单如下:
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
文件模块,仅可以读取上传文件目录下的文件。
** 云代码 2.0 开始将没有模块限制,但是上述必选的模块仍然将优先使用云代码环境中使用的版本**