月度归档:2013年08月

2013 年 8 月第四周 AVOSCloud 更新

8 月份的最后一周,我们的日常更新如期来到。本周最主要的改进是为每个 App 引入了 Master Key。原来提供的 Application Key 受限于 Class 权限和 Object 的 ACL 模型,在实现自己的后台管理平台的时候会遇到阻碍。

我们引入了 Master Key 之后,你可以使用 JavaScript SDK 的最新版本,设置 Master Key 来越过这些限制,任意地操作对象。

请切勿泄露应用的 App id、App Key 和 Master Key,这会对您的应用的数据安全性带来严重影响。

关于数据安全的策略和处理,我们会尽快写出一份详尽的文档,敬请期待。

本周我们升级了 JavaScript SDK 到 0.2.1 版本,最主要的改变就是上文提到引入了 Master Key。在云代码 Cloud Code 中,默认调用 JavaScript SDK 都是使用 Master Key 的超级权限。并且我们更新了 JavaScript SDK 的 API 文档 ,补充了 Cloud Code 相关函数和类的说明。

本周完整更新日志如下,最新 SDK 下载请到 这里

API 服务和网站

  • 每个 App 除了 Application Key 之外新增 Master Key,拥有超级权限,可越过 Class 权限和对象 ACL 设置来操作任何对象。Master Key 可以在应用设置页面看到。
  • 默认的文件表 (_File) 和设备安装表 (_Installation) 修改默认权限,文件默认不可删除,Installation 默认不可查询和删除,除非使用 Master Key 调用 REST API 或者登陆管理平台操作。
  • 修复邮件发送人中文乱码问题,修复更新用户重复发送确认邮件问题。感谢用户的反馈。
  • API 上传文件成功后返回 url 字段。

iOS SDK

  • 使用服务端的文件 URL,不再从 SDK 端构造文件 URL,保持 URL 的一致性
  • 发布 SinaWeiboUtils
  • 优化 file 相关的一些实现

Android SDK

  • 优化 push 路由,增加 push 集群支持
  • ParseFile 的 URL 由服务器端指定,不再由 sdk 端生成
  • 加入权限和服务检查,给出相应的提示 log,帮助开发人员更好地定位问题
  • 增加文件 mimeType 和从本地路径直接构造文件接口

JavaScript SDK

  • 升级到 0.2.1 版本
  • AV.initialize 方法加入第三个参数 Master Key,用于初始化 App。
  • 更新 SDK API 文档 ,补充 Cloud Code 相关函数和类的说明。

2013 年 8 月 23 号 AVOSCloud 更新

这周在兄弟们的努力下,我们再一次发布了每周更新。这次更新聚焦在客户端 SDK 和服务端的 Bug 修复和稳定性改进上。我们为所有的服务做了备份和容灾,没有一个 AVOSCloud 提供的服务是单点 (Single Point) 运行的。

我们的客户端 SDK,也在热心用户的帮助下,修复了一些 BUG,更加稳定和健壮。作为一个新产品,它必然会有一个逐渐的成熟过程,相信我们的努力,相信客户的帮助和使用,我们会越来越好。客户端 SDK 的最新稳定版本升级到 1.3.2,每周开始我们也将发布 SDK 的最新 beta 版本,及时修复 bug,增强功能,给愿意尝鲜的朋友使用。

本周,我们的网站还上线了站内通知功能,并且我们发送的邮件也允许您退订 (在个人设置页面),前几周因为时间和资源关系,邮件退订的功能一直暂未实现,如果对您造成了困扰,我们表示诚挚的道歉。

本次更新的完整日志如下,下载请到 下载页面 :

API 和网站

API 服务

  • App 内用户邮件验证选项正式生效。
  • 查询 include Pointer 的时候同时返回 type 和 className
  • 修改找不到 Class 的错误码为 101,兼容 parse API(原来为 404)
  • 移除云代码对 git.mei.fm 支持。
  • 云代码,在用户修改云代码仓库地址的时候删除老的仓库信息。
  • Push 服务增强对 iOS 的支持,可自动检测非活跃安装并设置 valid 为 false。

网站

  • 站内通知功能上线
  • 云代码删除对 gitlab 的支持,并自动删除 deploy key 中的评论(gitlab 的 bug)。
  • 用户退订邮件功能上线

iOS SDK 更新

  • 增加 fetch SDK 的判断,当 objectId 为空时,返回 missing objectId 错误
  • 解决再次登录后 [AVUser currentUser] 为空的问题
  • 解决 subclassing 的问题,当返回对象为自定义的子类化对象时,返回正确的 objective-c 对象,而非 AVObject
  • 解决 AVUser 的 objectForKey 当 key 不存在时抛出的异常问题
  • 解决 include 对象没有正确展开的问题
  • 解决 AVUser 关联 object 对象的保存问题
  • 解决文件缓存存在的问题功能

Android SDK 更新

  • 减少 avospush 库的大小
  • 调整 query 接口, 抛出异常,兼容 parse
  • 解决 subscribe 和 unsubscribe 引起脏数据问题
  • 增加 fetch SDK 的判断,当 objectId 为空时,返回 missing objectId 错误

如何使用 REST API 来做一个命令行 TODO 程序

AVOS Cloud RESTFul API

AVOS Cloud 除了提供几种设备的 SDK 让您可以进行移动 APP 的开发以外, 同样准备了一套 RESTFul API 接口可以供任何可以使用 HTTP 协议的设备和程序来连接 AVOS Cloud 并与之交互, 这样您可以自行开发使用 AVOS Cloud 的程序, 而且可以拥有 AVOS Cloud 的全部功能.

这篇文章中我们就尝试使用 REST API 用 Python 来与 AVOS Cloud 进行交互, 最终做出一个可以使用的 TODO 小程序.

开工前的准备

既然我们要做一个 APP, 那么首先要确定的是什么呢? 自然是确定这个程序到底要做什么事情, 对于 TODO 这种小程序来说, 我们的需求其实很简单:

  1. 可以用用户名和密码登陆, 登陆之后显示当前用户的 todo list
  2. 可以增加 TODO
  3. 可以将 TODO 状态改为完成
  4. 可以删除 TODO
  5. 所有的操作都随时与 AVOS Cloud 同步, 在任何可以运行代码的地方用户都能看到自己的 TODO list

当然这篇教程之后您可以在此基础上任意添加您想要的功能, 您可以随意使用教程中的源码.

注册 AVOS Cloud

既然需要 AVOS Cloud 来提供用户名密码等登陆方式, 自然需要在 cn.avoscloud.com 上面注册一个账户, 我们需要新建一个 APP 来存放我们的数据, 还需要在 APP 中新建一个 Todo 类来存放用户的 todo 信息. 这些都不需要写代码, 直接在网站上操作就可以了.

为了我们的程序能和 AVOS Cloud 服务器连接上, 我们需要 2 个信息, 一个是 Application Id, 另一个是 Application Key, 有了这两个信息以后将来我们就可以轻松地连接 AVOS Cloud 查看和操作我们的数据了.

连接 AVOS Cloud

AVOS Cloud 的 REST 接口需要我们自己发送不同的 HTTP 请求来与之交互, 在程序中我们用 httplib 来做到这一点, 同时再引入 json 和 urllib 来做 json 和 url 的编码解码工作.

在 Python 中, 建立连接的代码是这样的:

connection = httplib.HTTPSConnection('cn.avoscloud.com')
connection.connect()

我们可以用 connection 这个对象来发出和接受请求, 按照 RSET API 手册上所说的, 我们尝试一下注册一个用户, 他的用户名是 tester, 密码是 123456:

header = {
    "X-AVOSCloud-Application-Id" : "6w2rdw5x7l583ms221q7mchykbam9hr37yxvmhlsprtgxr39",
    "X-AVOSCloud-Application-Key" : "pr1o4ux3og6qe9htuvuaklv348b7oqa73uqz30uyhwo3c6hi",
    "Content-Type" : "application/json",
}
body_d = {
        "username": "tester",
        "password": "123456",
}
body = json.dumps(body_d)
connection.request('POST','/1/users',body,header)

注意这里的”X-AVOSCloud-Application-Id” 和”X-AVOSCloud-Application-Key” 就是我们在注册 AVOS Cloud 的时候得到的 Application Id 和 Application Key, 如果发送的字符串是经过 json 编码的话需要在 header 中指定 Content-Type. 我们通过 connection.request 方法发送一个请求, 接着可以通过 connection.getresponse 方法得到 AVOSCloud 返回给我们的结果:

response = connection.getresponse()
result = json.loads(response.read())

这里需要用 json.loads 来把 response 返回的 json 字符串转化为 python 数据结构.

最后用 connection.close() 结束这次连接:

connection.close()

上面这 3 步就是每一次对 AVOSCloud 做请求所经历的步骤, 在附件中提供的代码的 restlib.py 中已经用一个 AVConnection 封装了, 每一次连接只需要使用 with 语句即可 (不要忘了改 Application Id 和 Application Key):

with AVConnection() as a:
    result = a.request(method,rul,body,header)
return result

完成本地逻辑

对于我们之前定义的极为简单的 todo 程序, 如果我们要把数据保存在本地, 其实这个程序就非常简单, 难度大致上就是比 Hello World 稍微难一点的水准. 在完成本地的基础上, 您可以发现实际上把程序改成 AVOSCloud 版本的话只需要很少的工作就可以.

###显示 TODO List

对于每个 TODO 都有相应的状态 (是否完成), 也有相应的序号以供之后进行增加和删除操作用, 为了确认用户是否成功操作, 应该在每次操作后都显示现有的 TODO List, 我们假设把 todo 封装在 todolist 里面:

def print_todolist(todolist):
    if len(todolist) == 0:
        print "No todos now!"
    for i,todo in enumerate(todolist):
        done_q = "DONE" if todo[u"done"] else "TODO"
        print "%3d : %s : %s" % (i, done_q, todo[u'content'])

注意每一个我们从 AVOS Cloud 中取回的对象都是经过 UTF-8 编码后的对象, 所以在 Python 2 中我们对于这些对象都应使用 u” 字符串的形式, 这样可以方便的支持多国语言.

###操作循环

我们用最简单的循环方式实现在命令行里面用户的操作:

command = raw_input("Input q to quit, 0 to add , 1 to toggle, 2 to remove:")
while (command != 'q'):
    if command == "0":
        ctr_add_todo(user_objectId)
    elif command == "1":
        ctr_toggle_todo(todolist)
    elif command == "2":
        ctr_remove_todo(todolist)
    else:
        print "Not correct command!"
    todolist = retrive_todolist(user_objectId)
    print_todolist(todolist)
    command = raw_input("Input q to quit, 0 to add , 1 to toggle, 2 to remove:")

其中 ctr_add_todo 等函数涉及与 AVOS Cloud 的交互,user_objectId 保存的当前用户的用户 id. 这些在下面都有解释, 这里只用看下整个 while 的结构即可.

与 AVOS Cloud 交互

增加/修改/删除 TODO

这些操作在 RESTFul API 中均有定义, 只需要按照需求发送相应的请求即可, 例如我们要修改一个 TODO 的状态, 只需要传入 {“done”:done} 到相应的 TODO 的 objectId 上即可更新这个 TODO 的状态:

def update_obj(clazz,objectId,obj):
    body = json.dumps(obj)
    with AVConnection() as a:
        result = a.json_request('PUT','/1/classes/'+clazz+'/'+objectId,body)
    return result
def update_todo(todo_objectId,done):
    update_obj("Todo",todo_objectId,{"done":done})

对于增加的 TODO 来说, 我们需要指定这个 TODO 所隶属的用户, 这里需要一个指针指向我们当前用户:

def create_obj(clazz,obj):
    body = json.dumps(obj)
    with AVConnection() as a:
        result = a.json_request('POST','/1/classes/'+clazz,body)
    return result
def add_todo(user_objectId,content,done=False):
    obj = {
        "content" : content,
        "user" : {
            "__type": "Pointer",
            "className": "_User",
            "objectId": user_objectId,
        },
        "done" : done,
    }
    todo = create_obj("Todo",obj)

获取 TODO

对于我们当前用户来说, 我们要获取 TODO 的 user 字段为当前 User, 这里就需要我们使用 query 来获取符合相应条件的内容:

def query_objs(clazz,condition):
    params = urllib.urlencode({"where":json.dumps(condition)})
    with AVConnection() as a:
        result = a.request('GET',('/1/classes/'+clazz+'?%s') % params , '')
    return result
def retrive_todolist(user_objectId):
    user_condition = {
        "user" : {
            "__type": "Pointer",
            "className": "_User",
            "objectId": user_objectId,
        }
    }
    result = query_objs("Todo",user_condition)
    if result[1] != 200:
        print "Not Found!"
        return
    todolist = result[0][u"results"]
    return todolist

Query 有很多高级的用法来查询, 您可以查阅 RESTFul API 来获得更详细的信息.

继续探索

我们只是用到了很少一些 AVOS Cloud 的功能就已经能做出一个功能基本成型的云同步的待办事项程序, 您可以看到从传统程序出发只需很少的工作量即可让您的程序与云端同步, 您可以尽情地发挥您的想象力和创造力来使用 AVOS Cloud.
您可以通过任何有网络连接的设备来与 AVOS Cloud 交互, 希望您能

这个 TODO 程序远不是结束

您可以看到这个 TODO 程序还是非常简陋的, 它甚至没有安全性, 任何用户都可以访问别人的 TODO(如果发送相应的请求的话), 要完善这一点的话, 您需要在创建这个对象的时候指定 ACL, 这里留给您自行探索 (请查看我们的 RESTFul API 文档).

##源码

restlib.py

import httplib,json,urllib
class AVConnection:
    def __enter__(self):
        self.Application_Id = "6w2rdw5x7l583ms221q7mchykbam9hr37yxvmhlsprtgxr39"
        self.Application_Key = "pr1o4ux3og6qe9htuvuaklv348b7oqa73uqz30uyhwo3c6hi"
        self.connection = httplib.HTTPSConnection('cn.avoscloud.com')
        self.connection.connect()
        return self
    def request(self,method,url,body,header={}):
        header["X-AVOSCloud-Application-Id"] = self.Application_Id
        header["X-AVOSCloud-Application-Key"] = self.Application_Key
        self.connection.request(method,url,body,header)
        response = self.connection.getresponse()
        result = json.loads(response.read())
        status = response.status
        return result,status
    def json_request(self,method,url,body,header={}):
        header["Content-Type"]="application/json"
        return self.request(method,url,body,header)
    def __exit__(self, type, value, traceback):
        self.connection.close()
def login(username,password):
    params_d = {
        "username":username,
        "password":password,
        }
    params = urllib.urlencode(params_d)
    res = {"login":False}
    with AVConnection() as a:
        result = a.request('GET','/1/login?%s' % params, '',{})
        if result[1] == 200:
            res.update({"login":True,"body":result[0]})
        else:
            res["reason"] = result[0][u'error']
    return res
def signup(username,password,info={}):
    body_d = {
            "username": username,
            "password": password,
        }
    body_d.update(info)
    body = json.dumps(body_d)
    with AVConnection() as a:
        result = a.json_request('POST','/1/users',body)
    return result
def create_obj(clazz,obj):
    body = json.dumps(obj)
    with AVConnection() as a:
        result = a.json_request('POST','/1/classes/'+clazz,body)
    return result
def update_obj(clazz,objectId,obj):
    body = json.dumps(obj)
    with AVConnection() as a:
        result = a.json_request('PUT','/1/classes/'+clazz+'/'+objectId,body)
    return result
def get_obj(clazz,objectId):
    with AVConnection() as a:
        result = a.request('GET','/1/classes/'+clazz+'/'+objectId,'')
    return result
def remove_obj(clazz,objectId):
    with AVConnection() as a:
        result = a.request('DELETE','/1/classes/'+clazz+'/'+objectId,'')
    return result
def query_objs(clazz,condition):
    params = urllib.urlencode({"where":json.dumps(condition)})
    with AVConnection() as a:
        result = a.request('GET',('/1/classes/'+clazz+'?%s') % params , '')
    return result

todo.py

import restlib,getpass
def retrive_todolist(user_objectId):
    user_condition = {
        "user" : {
            "__type": "Pointer",
            "className": "_User",
            "objectId": user_objectId,
        }
    }
    result = restlib.query_objs("Todo",user_condition)
    if result[1] != 200:
        print "Not Found!"
        return
    todolist = result[0][u"results"]
    return todolist
def add_todo(user_objectId,content,done=False):
    obj = {
        "content" : content,
        "user" : {
            "__type": "Pointer",
            "className": "_User",
            "objectId": user_objectId,
        },
        "done" : done,
    }
    todo = restlib.create_obj("Todo",obj)
def delete_todo(todo_objectId):
    restlib.remove_obj("Todo",todo_objectId)
def update_todo(todo_objectId,done):
    restlib.update_obj("Todo",todo_objectId,{"done":done})
def print_todolist(todolist):
    if len(todolist) == 0:
        print "No todos now!"
    for i,todo in enumerate(todolist):
        done_q = "DONE" if todo[u"done"] else "TODO"
        print "%3d : %s : %s" % (i, done_q, todo[u'content'])
def ctr_add_todo(user_objectId):
    content = raw_input("Content:")
    add_todo(user_objectId,content)
def ctr_toggle_todo(todolist):
    idx = input("Which one?")
    todo_objectId = todolist[idx][u'objectId']
    done = not todolist[idx][u'done']
    update_todo(todo_objectId,done)
def ctr_remove_todo(todolist):
    idx = input("Which one?")
    todo_objectId = todolist[idx][u'objectId']
    delete_todo(todo_objectId)
def main():
    username = raw_input("Username:")
    password = getpass.getpass("Password:")
    result = restlib.login(username,password)
    if not result["login"]:
        print result["reason"]
        return
    user_objectId = result["body"][u'objectId']
    todolist = retrive_todolist(user_objectId)
    print_todolist(todolist)
    command = raw_input("Input q to quit, 0 to add , 1 to toggle, 2 to remove:")
    while (command != 'q'):
        if command == "0":
            ctr_add_todo(user_objectId)
        elif command == "1":
            ctr_toggle_todo(todolist)
        elif command == "2":
            ctr_remove_todo(todolist)
        else:
            print "Not correct command!"
        todolist = retrive_todolist(user_objectId)
        print_todolist(todolist)
        command = raw_input("Input q to quit, 0 to add , 1 to toggle, 2 to remove:")
if __name__ == '__main__':
    main()

GEO Point 的使用

GEO Point 是什么

在 APP 开发的过程中, 我们经常会遇到在针对于地理坐标的一些问题. 而现实的地理坐标在我们的 AVOS Cloud 上提供一个对应, 我们把它称之为 GEO Point. 每一个 GEO Point 只包含 2 个信息, 就是它的经度 (longtitude) 与纬度 (latitude).

下面我们建立一个北纬 40 度, 东经 30 度的点:

JavaScript:

var point = new AV.GeoPoint({latitude: 40.0, longitude: -30.0});

(JavaScript 的 SDK 下载到 这里 。)

这只是一个代表这坐标信息的点而已, 而我们需要把这个点关联到对象上去, 这样才构成了一个有地理位置信息的对象

JavaScript:

placeObject.set("location", point);

这样我们就为 placeObject 这个对象加入了地理位置信息了. 在之后我们可以用 SDK 提供的功能进行查找.

做一个” 查找附近的人” 的功能

有了 AVOS Cloud 以后, 您可以轻松地实现类似于微信中:” 查找附近的人” 这种功能. 而这并不需要您做太多的工作, 大部分事情都可以交给 AVOS Cloud 来做.

为每个用户加入地理位置信息

为了提供这项功能, 当然确定用户的地理位置是一定需要的, 获取用户当前的地理位置需要根据用户所用的设备来调用相应设备的 API, 以获取当前用户的地理位置信息. 然后如果需要的话应该将其转换为经度和纬度, 我们这里假设用户在东经 40 度北纬 50 度的地方, 然后设定当前用户的地理位置, 同时为了让其他用户能得到当前用户的地址, 我们应该更新当前用户的地理信息:

var point = new AV.GeoPoint({latitude: 40.0, longitude: -30.0});
var currentUser = AV.User.current();
if (currentUser) {
    currentUser.set("location", point);
    currentUser.save();
} else {
    // user signup or login
}

查找当前用户附近的用户

有了用户的地理信息之后, 我们就可以提供一个搜索了, 这一点可以用 AVOS Cloud 带有的地理查询功能来做:

var query = new AV.Query(Parse.User);
query.near("location", point);
query.limit(10);
query.find({
  success: function(users) {
  }
});

AVOS Cloud 对于 near 类型的查询会按距离由近到远进行排序, 这样我们非常轻松地就找到了离我们的当前用户距离最近的十个用户了.

GEO Point 还有哪些查询方式?

上面的例子中我们限定了返回 10 个结果, 但是如果我们只想查找同城的用户的话怎么办呢?

可以用 withinKilometers 方式来限定我们的查询, 或者说是用 withinGeoBox 这个方法把结果限定在一个矩形之内.
withinKilometers 接受一个距离参数, 可以限定 query 的距离范围. 而 withinGeoBox 接受 2 个 GEO Point, 这两个点代表了矩形的对角的两个点, 这样就可以把查询限定在一个矩形范围内了.

2013 年 8 月 15 号 AVOSCloud 更新

AVOSCloud 本周更新,最重要的新闻当然是我们正式发布了 Web Hosting 功能。您可以在 AVOSCloud 上托管您的网站啦。具体使用见已经更新过的 Cloud Code 指南

我们提供了两个线上 demo:

我们还第一次发布 JavaScript SDK 指南

本周服务端 API 和客户端 SDK 也做了一些 bug 修复的工作。

完整的更新日志如下:

服务端和管理平台

Android 客户端 SDK 1.3.1

  • 解决 installation channel 空指针 bug

iOS SDK 1.3.1

  • 修改 CocoaPods 增加 icu 库的依赖

SDK 下载链接: https://cn.avoscloud.com/docs/sdk_down.html

2013 年 8 月 8 号 AVOSCloud 更新

又到一周更新时,本周我们发布了一个小更新,从今天开始,您就可以从数据管理界面直接上传或者删除文件。

uploading_files.png

其次,我们开放了 web 主机设置,预计下周将正式发布 web 主机托管功能,但是现在你可以在应用设置页面的 web 主机菜单里,先抢注一个酷酷的域名了。所有设置的域名都将是 avosapp.com 的二级域名。

最最重要的是,我们终于 开放注册 了,欢迎使用 AVOSCloud。

web_hosting.png

我们还发布了完整的 REST API 文档

完整的更新日志如下:

iOS 和 android 客户端 SDK 1.3.0

  • 增加 selectKeys 支持,允许调用方指定 keys,服务器端仅返回 keys 对应的数据,节省流量和资源。
  • 增加文件 key 长度。
  • 修改 sdk 的 user agent。
  • 发布 Javascript SDK 0.2,支持文件上传 AV.File

API 和 Web 管理平台更新

  • 查询接口支持 keys 参数,可指定查询返回的 keys 列表,节省流量。
  • 现在可以直接从 dashboard 上传和删除文件
  • 云代码展示平台的 deploy key,如果是私有仓库可以将该 key 加入你的 deploy key,就可以使用云代码部署私有仓库代码。
  • 应用设置新增「web 主机」配置,该功能开放后可在 AVOSCloud 托管您的虚拟主机 (web hosting),目前您可以先抢注一个二级域名,每个 App 只能绑定一个二级域名。
  • 发布了完整的 REST API 文档
  • 开放注册,欢迎使用 AVOSCloud。