跳到主要内容

数据存储开发指南 · Python

数据存储是云服务提供的核心功能之一,可用于存放和查询应用数据。下面的代码展示了如何创建一个对象并将其存入云端:

# 声明 class
Todo = leancloud.Object.extend('Todo')

# 构建对象
todo = Todo()

# 为属性赋值
todo.set('title', '工程师周会')
todo.set('content', '周二两点,全体成员')

# 将对象保存到云端
todo.save()

我们为各个平台或者语言开发的 SDK 在底层都是通过 HTTPS 协议调用统一的 REST API,提供完整的接口对数据进行各类操作。

SDK 安装与初始化

请阅读 Python SDK 安装指南

对象

leancloud.Object

leancloud.Object 是云服务对复杂对象的封装,每个 leancloud.Object 包含若干与 JSON 格式兼容的属性值对(也称键值对,key-value pairs)。这个数据是无模式化的(schema free),意味着你不需要提前标注每个 leancloud.Object 上有哪些 key,你只需要随意设置键值对就可以,云端会保存它。

比如说,一个保存着单个 Todo 的 leancloud.Object 可能包含如下数据:

title:      "给小林发邮件确认会议时间",
isComplete: false,
priority: 2,
tags: ["工作", "销售"]

数据类型

leancloud.Object 支持的数据类型包括 StringNumberBooleanObjectArrayDate 等等。你可以通过嵌套的方式在 ObjectArray 里面存储更加结构化的数据。

leancloud.Object 还支持两种特殊的数据类型 PointerFile,可以分别用来存储指向其他 leancloud.Object 的指针以及二进制数据。

leancloud.Object 同时支持 GeoPoint,可以用来存储地理位置信息。参见 GeoPoint

以下是一些示例:

from datetime import datetime

# 基本类型
bool = True
number = 2018
string = 'Top Hit Songs'
date = datetime.now()
list = [string, number]
dictionary = {
'number': number,
'string': string
}

# 构建对象
TestObject = leancloud.Object.extend('TestObject')
test_object = TestObject()
test_object.set('testString', string)
test_object.set('testNumber', number)
test_object.set('testBoolean', bool)
test_object.set('testList', list)
test_object.set('testDict', dictionary)
test_object.set('testDate', date)
test_object.save()

我们不推荐通过 byte[]leancloud.Object 里面存储图片、文档等大型二进制数据。每个 leancloud.Object 的大小不应超过 128 KB。如需存储大型文件,可创建 leancloud.File 实例并将将其关联到 leancloud.Object 的某个属性上。参见 文件

注意:时间类型在云端将会以 UTC 时间格式存储,但是客户端在读取之后会转化成本地时间。

云服务控制台 > 数据存储 > 结构化数据 中展示的日期数据也会依据操作系统的时区进行转换。一个例外是当你通过 REST API 获得数据时,这些数据将以 UTC 呈现。你可以手动对它们进行转换。

若想了解云服务是如何保护应用数据的,请阅读数据和安全

构建对象

下面的代码构建了一个 class 为 Todoleancloud.Object

# 为 leancloud.Object 创建子类
Todo = leancloud.Object.extend('Todo')

# 为该类创建一个新实例
todo = Todo()

在构建对象时,为了使云端知道对象属于哪个 class,需要将 class 的名字作为参数传入。你可以将云服务里面的 class 比作关系型数据库里面的表。一个 class 的名字必须以字母开头,且只能包含数字、字母和下划线。

保存对象

下面的代码将一个 class 为 Todo 的对象存入云端:

# 声明 class
Todo = leancloud.Object.extend('Todo')

# 构建对象
todo = Todo()

# 为属性赋值
todo.set('title', '马拉松报名')
todo.set('priority', 2)

# 将对象保存到云端
todo.save()

为了确认对象已经保存成功,我们可以到 云服务控制台 > 数据存储 > 结构化数据 > Todo 里面看一下,应该会有一行新的数据产生。点一下这个数据的 objectId,应该能看到类似这样的内容:

{
"title": "马拉松报名",
"priority": 2,
"ACL": {
"*": {
"read": true,
"write": true
}
},
"objectId": "582570f38ac247004f39c24b",
"createdAt": "2017-11-11T07:19:15.549Z",
"updatedAt": "2017-11-11T07:19:15.549Z"
}

注意,无需在 云服务控制台 > 数据存储 > 结构化数据 里面创建新的 Todo class 即可运行前面的代码。如果 class 不存在,它将自动创建。

以下是一些对象的内置属性,会在对象保存时自动创建,无需手动指定:

内置属性类型描述
objectIdString该对象唯一的 ID 标识。
ACLAV.ACL该对象的权限控制,实际上是一个 JSON 对象,控制台做了展现优化。
createdAtDate该对象被创建的时间。
updatedAtDate该对象最后一次被修改的时间。

这些属性的值会在对象被存入云端时自动填入,代码中尚未保存的 leancloud.Object 不存在这些属性。

属性名(keys)只能包含字母、数字和下划线。自定义属性不得以双下划线(__)开头或与任何系统保留字段和内置属性(ACLclassNamecreatedAtobjectIdupdatedAt)重名,无论大小写。

属性值(values)可以是字符串、数字、布尔值、数组或字典(任何能以 JSON 编码的数据)。参见 数据类型

我们推荐使用驼峰式命名法(CamelCase)为类和属性来取名。类,采用大驼峰法,如 CustomData。属性,采用小驼峰法,如 imageUrl

获取对象

对于已经保存到云端的 leancloud.Object,可以通过它的 objectId 将其取回:

Todo = leancloud.Object.extend('Todo')
query = Todo.query
todo = query.get('582570f38ac247004f39c24b')

# todo 就是 objectId 为 582570f38ac247004f39c24b 的 Todo 实例
title = todo.get('title')
priority = todo.get('priority')

# 获取内置属性
object_id = todo.id
update_at = todo.updated_at
created_at = todo.created_at

对象拿到之后,可以通过 get 方法来获取各个属性的值。注意 objectIdupdatedAtcreatedAt 这三个内置属性不能通过 get 获取或通过 set 修改,只能由云端自动进行填充。尚未保存的 leancloud.Object 不存在这些属性。

如果你试图获取一个不存在的属性,SDK 不会报错,而是会返回 None

同步对象

当云端数据发生更改时,你可以调用 fetch 方法来刷新对象,使之与云端数据同步:

Todo = leancloud.Object.extend('Todo')
todo = Todo.create_without_data('582570f38ac247004f39c24b')
todo.fetch()

更新对象

要更新一个对象,只需指定需要更新的属性名和属性值,然后调用 save 方法。例如:

Todo = leancloud.Object.extend('Todo')
todo = Todo.create_without_data('582570f38ac247004f39c24b')
todo.set('content', '这周周会改到周三下午三点。')
todo.save()

云服务会自动识别需要更新的属性并将对应的数据发往云端,未更新的属性会保持原样。

有条件更新对象

通过传入 query 选项,可以按照指定条件去更新对象——当条件满足时,执行更新;条件不满足时,不执行更新并返回 305 错误。

例如,用户的账户表 Account 有一个余额字段 balance,同时有多个请求要修改该字段值。为避免余额出现负值,只有当金额小于或等于余额的时候才能接受请求:

Account = leancloud.Object.extend('Account')
account = Account.create_without_data('5745557f71cfe40068c6abe0')
# 对 balance 原子减少 100
amount = -100
account.increment('balance', amount)
# 设置条件
where = Account.query.greater_than_or_equal_to('balance', -amount)
# 操作结束后,返回最新数据。
# 如果是新对象,则所有属性都会被返回,
# 否则只有更新的属性会被返回。
account.fetch_when_save = True
try:
account.save(where=where)
print('当前余额为:', account.get('balance'))
except leancloud.LeanCloudError as e:
if e.code == 305:
print('余额不足,操作失败!')
else:
raise

query 选项只对已存在的对象有效,不适用于尚未存入云端的对象。

query 选项在有多个客户端需要更新同一属性的时候非常有用。相比于通过 leancloud.Query 查询 leancloud.Object 再对其进行更新的方法,这样做更加简洁,并且能够避免出现差错。

更新计数器

设想我们正在开发一个微博,需要统计一条微博有多少个赞和多少次转发。由于赞和转发的操作可能由多个客户端同时进行,直接在本地更新数字并保存到云端的做法极有可能导致差错。为保证计数的准确性,可以通过 原子操作 来增加或减少一个属性内保存的数字:

post.increment('likes', 1)

可以指定需要增加或减少的值。若未指定,则默认使用 1

注意,虽然原子增减支持浮点数,但因为底层数据库的浮点数存储格式限制,会有舍入误差。 因此,需要原子增减的字段建议使用整数以避免误差,例如 3.14 可以存储为 314,然后在客户端进行相应的转换。 否则,以比较大小为条件查询对象的时候,需要特殊处理, < a 需改查 < a + e> a 需改查 > a - e== a 需改查 > a - e< a + e,其中 e 为误差范围,据所需精度取值,比如 0.0001

更新数组

更新数组也是原子操作。使用以下方法可以方便地维护数组类型的数据:

  • add() 将指定对象附加到数组末尾。
  • add_unique() 如果数组中不包含指定对象,则将该对象加入数组。对象的插入位置是随机的。
  • remove() 从数组字段中删除指定对象的所有实例。

例如,Todo 用一个 alarms 属性保存所有闹钟的时间。下面的代码将多个时间加入这个属性:

from datetime import datetime

alarm1 = datetime(2018, 4, 30, 7, 10, 00)
alarm2 = datetime(2018, 4, 30, 7, 20, 00)
alarm3 = datetime(2018, 4, 30, 7, 30, 00)

Todo = leancloud.Object.extend('Todo')
todo = Todo()
todo.add_unique('alarms', alarm1)
todo.add_unique('alarms', alarm2)
todo.add_unique('alarms', alarm3)
todo.save()

删除对象

下面的代码从云端删除一个 Todo 对象:

Todo = leancloud.Object.extend('Todo')
todo = Todo.create_without_data('582570f38ac247004f39c24b')
todo.destroy()

注意,删除对象是一个较为敏感的操作,我们建议你阅读ACL 权限管理开发指南 来了解潜在的风险。熟悉 class 级别、对象级别和字段级别的权限可以帮助你有效阻止未经授权的操作。

批量操作

可以在一次请求中包含多个构建、保存、删除和同步对象的操作:

# 批量构建和更新
leancloud.Object.save_all(list_of_objects)

# 批量删除
leancloud.Object.destroy_all(list_of_objects)

下面的代码将所有 TodoisComplete 设为 true

Todo = leancloud.Object.extend('Todo')
# 获取需要更新的 todo
todo1 = Todo()
todo2 = Todo()
todo3 = Todo()
# 更新属性值
todo1.set('isComplete', True)
todo2.set('isComplete', True)
todo3.set('isComplete', True)
# 批量更新
Todo.save_all([todo1, todo2, todo3])

虽然上述方法可以在一次请求中包含多个操作,每一个分别的保存或同步操作在计费时依然会被算作一次请求,而所有的删除操作则会被合并为一次请求。

数据模型

对象之间可以产生关联。拿一个博客应用来说,一个 Post 对象可以与许多个 Comment 对象产生关联。云服务支持三种关系:一对一、一对多、多对多。

一对一、一对多关系

一对一、一对多关系可以通过将 leancloud.Object 保存为另一个对象的属性值的方式产生。比如说,让博客应用中的一个 Comment 指向一个 Post

下面的代码会创建一个含有单个 CommentPost

# 创建 post
Post = leancloud.Object.extend('Post')
post = Post()
post.set('title', '饿了……')
post.set('content', '中午去哪吃呢?')

# 创建 comment
Comment = leancloud.Object.extend('Comment')
comment = Comment()
comment.set('content', '当然是肯德基啦!')

# 将 post 设为 comment 的一个属性值
comment.set('parent', post)

# 保存 comment 会同时保存 post
comment.save()

云端存储时,会将被指向的对象用 Pointer 的形式存起来。你也可以用 objectId 来指向一个对象:

Post = leancloud.Object.extend('Post')
post = Post.create_without_data('57328ca079bc44005c2472d0')
comment.set('post', post)

请参阅 关系查询 来了解如何获取关联的对象。

多对多关系

想要建立多对多关系,最简单的办法就是使用 数组。在大多数情况下,使用数组可以有效减少查询的次数,提升程序的运行效率。但如果有额外的属性需要附着于两个 class 之间的关联,那么使用 中间表 可能是更好的方式。注意这里说到的额外的属性是用来描述 class 之间的关系的,而不是任何单一的 class 的。

我们建议你在任何一个 class 的对象数量超出 100 的时候考虑使用中间表。

线程安全

leancloud.Object 目前不是线程安全的,因此请避免多个线程修改同一个 leancloud.Object 实例的操作。如果遇到必须多线程操作的情况,需要根据情况加锁。

查询

我们已经了解到如何从云端获取单个 leancloud.Object,但你可能还会有一次性获取多个符合特定条件的 leancloud.Object 的需求,这时候就需要用到 leancloud.Query 了。

基础查询

执行一次基础查询通常包括这些步骤:

  1. 构建 leancloud.Query
  2. 向其添加查询条件;
  3. 执行查询并获取包含满足条件的对象的数组。

下面的代码获取所有 lastNameSmithStudent

Student = leancloud.Object.extend('Student')
query = Student.query
# 以上两行等价于 query = leancloud.Query('Student')
query.equal_to('lastName', 'Smith')
student_list = query.find()

查询条件

可以给 leancloud.Object 添加不同的条件来改变获取到的结果。

下面的代码查询所有 firstName 不为 Jack 的对象:

query.not_equal_to("firstName", 'Jack')

对于能够排序的属性(比如数字、字符串),可以进行比较查询:

# 限制 age < 18
query.less_than('age', 18)

# 限制 age <= 18
query.less_than_or_equal_to('age', 18)

# 限制 age > 18
query.greater_than('age', 18)

# 限制 age >= 18
query.greater_than_or_equal_to('age', 18)

可以在同一个查询中设置多个条件,这样可以获取满足所有条件的结果。可以理解为所有的条件是 AND 的关系:

query.equal_to("firstName", 'Jack')
query.greater_than('age', 18)

可以通过指定 limit 限制返回结果的数量(默认为 100):

# 最多获取 10 条结果
query.limit(10)

由于性能原因,limit 最大只能设为 1000。即使将其设为大于 1000 的数,云端也只会返回 1,000 条结果。

如果只需要一条结果,可以直接用 first

Todo = leancloud.Object.extend('Todo')
query = Todo.query
query.equal_to('priority', 2)
todo = query.first()

可以通过设置 skip 来跳过一定数量的结果:

# 跳过前 20 条结果
query.skip(20)

skiplimit 结合起来,就能实现翻页功能:

Todo = leancloud.Object.extend('Todo')
query = Todo.query
query.equal_to('priority', 2)
query.limit(10)
query.skip(20)

需要注意的是,skip 的值越高,查询所需的时间就越长。作为替代方案,可以通过设置 createdAtupdatedAt 的范围来实现更高效的翻页,因为它们都自带索引。 同理,也可以通过设置自增字段的范围来实现翻页。

对于能够排序的属性,可以指定结果的排序规则:

# 按 createdAt 升序排列
query.ascending('createdAt')

# 按 createdAt 降序排列
query.descending('createdAt')

还可以为同一个查询添加多个排序规则:

query.add_ascending('createdAt')
query.add_descending('priority')

下面的代码可用于查找包含或不包含某一属性的对象:

# 查找包含 'images' 的对象
query.exists('images')

# 查找不包含 'images' 的对象
query.does_not_exist('images')

可以通过 select 指定需要返回的属性。下面的代码只获取每个对象的 titlecontent(包括内置属性 objectIdcreatedAtupdatedAt):

Todo = leancloud.Object.extend('Todo')
query = Todo.query
query.select('title', 'content')
todo = query.first()

title = todo.get('title') # √
content = todo.get('content') # √
notes = todo.get('notes') # None

select 支持点号(author.firstName),详见《点号使用指南》

另外,字段名前添加减号前缀表示反向选择,例如 -author 表示不返回 author 字段。 反向选择同样适用于内置字段,比如 -objectId,也可以和点号组合使用,比如 -pubUser.createdAt

对于未获取的属性,可以通过对结果中的对象进行 fetch 操作来获取。参见 同步对象

字符串查询

可以用 startsWith 来查找某一属性值以特定字符串开头的对象。和 SQL 中的 LIKE 一样,你可以利用索引带来的优势:

Todo = leancloud.Object.extend('Todo')
query = Todo.query
# 相当于 SQL 中的 title LIKE 'lunch%'
query.startswith("title", "lunch")

可以用 contains 来查找某一属性值包含特定字符串的对象:

Todo = leancloud.Object.extend('Todo')
query = Todo.query
# 相当于 SQL 中的 title LIKE '%lunch%'
query.contains("title", "lunch")

startsWith 不同,contains 无法利用索引,因此不建议用于大型数据集。

注意 startsWithcontains 都是 区分大小写 的,所以上述查询会忽略 LunchLUNCH 等字符串。

如果想查找某一属性值不包含特定字符串的对象,可以使用 matches 进行基于正则表达式的查询:

Todo = leancloud.Object.extend('Todo')
query = Todo.query
# 'title' 不包含 'ticket'(不区分大小写)
query.matched('title', '^((?!ticket).)*$', ignore_case=True)

不过我们并不推荐大量使用这类查询,尤其是对于包含超过 100,000 个对象的 class, 因为这类查询无法利用索引,实际操作中云端会遍历所有对象来获取结果。如果有进行全文搜索的需求,可以使用全文搜索服务。

使用查询时如果遇到性能问题,可参阅 查询性能优化

数组查询

下面的代码查找所有数组属性 tags 包含 工作 的对象:

query.equal_to('tags', '工作')

下面的代码查询数组属性长度为 3(正好包含 3 个标签)的对象:

query.size_equal_to('tags', 3)

下面的代码查找所有数组属性 tags 同时包含 工作销售会议 的对象:

query.contains_all('tags', ['工作', '销售', '会议'])

如需获取某一属性值包含一列值中任意一个值的对象,可以直接用 contained_in 而无需执行多次查询。下面的代码构建的查询会查找所有 priority1 2 的 todo 对象:

# 单个查询
Todo = leancloud.Object.extend('Todo')
priority_one_or_two = Todo.query
priority_one_or_two.contained_in('priority', [1, 2])
# 这样就可以了 :)

# ---------------
# vs.
# ---------------

# 多个查询
Todo = leancloud.Object.extend('Todo')

priority_one = Todo.query
priority_one.equal_to('priority', 1)

priority_two = Todo.query
priority_two.equal_to('priority', 2)

priority_one_or_two = leancloud.Query.or_(priority_one, priority_two)
# 好像有些繁琐 :(

反过来,还可以用 not_contained_in 来获取某一属性值不包含一列值中任何一个的对象。

关系查询

查询关联数据有很多种方式,常见的一种是查询某一属性值为特定 leancloud.Object 的对象,这时可以像其他查询一样直接用 equal_to。比如说,如果每一条博客评论 Comment 都有一个 post 属性用来存放原文 Post,则可以用下面的方法获取所有与某一 Post 相关联的评论:

Post = leancloud.Object.extend('Post')
post = Post.create_without_data('57328ca079bc44005c2472d0')
query = leancloud.Query('Comment')
query.equal_to('post', post)
comment_list = query.find()

如需获取某一属性值为另一查询结果中任一 leancloud.Object 的对象,可以用 matches_query。下面的代码构建的查询可以找到所有包含图片的博客文章的评论:

inner_query = leancloud.Query('Post')
inner_query.exists('images')

query = leancloud.Query('Comment')
query.matches_query('post', inner_query)

如需获取某一属性值不是另一查询结果中任一 leancloud.Object 的对象,则使用 does_not_match_query

有时候可能需要获取来自另一个 class 的数据而不想进行额外的查询,此时可以在同一个查询上使用 include。下面的代码查找最新发布的 10 条评论,并包含各自对应的博客文章:

query = leancloud.Query('Comment')

# 获取最新发布的
query.add_descending('createdAt')

# 只获取 10 条
query.limit(10)

# 同时包含博客文章
query.include('post')

comment_list = query.find()
for comment in comment_list:
# 该操作无需网络连接
post = comment.get('post')

可以用 dot 符号(.)来获取多级关系,例如 post.author,详见《点号使用指南》的《在查询对象时使用点号》一节。

可以在同一查询上应用多次 include 以包含多个属性。通过这种方法获取到的对象同样接受 firstgetleancloud.Query 辅助方法。

通过 include 进行多级查询的方式不适用于数组属性内部的 leancloud.Object,只能包含到数组本身。

关系查询的注意事项

云端使用的并非关系型数据库,无法做到真正的联表查询,所以实际的处理方式是:先执行内嵌/子查询(和普通查询一样,limit 默认为 100,最大 1000),然后将子查询的结果填入主查询的对应位置,再执行主查询。如果子查询匹配到的记录数量超出 limit,且主查询有其他查询条件,那么可能会出现没有结果或结果不全的情况,因为只有 limit 数量以内的结果会被填入主查询。

我们建议采用以下方案进行改进:

  • 确保子查询的结果在 100 条以下,如果在 100 至 1,000 条之间的话请将子查询的 limit 设为 1000
  • 将需要查询的字段冗余到主查询所在的表上。
  • 进行多次查询,每次在子查询上设置不同的 skip 值来遍历所有记录(注意 skip 的值较大时可能会引发性能问题,因此不是很推荐)。

统计总数量

如果只需知道有多少对象匹配查询条件而无需获取对象本身,可使用 count 来代替 find。比如说,查询有多少个已完成的 todo:

Todo = leancloud.Object.extend('Todo')
query = Todo.query
query.equal_to('isComplete', True)
count = query.count()

组合查询

组合查询就是把诸多查询条件用一定逻辑合并到一起(ORAND)再交给云端去查询。

组合查询不支持在子查询中包含 GeoPoint 或其他非过滤性的限制(例如 nearwithinGeoBoxlimitskipascendingdescendinginclude)。

OR 查询

OR 操作表示多个查询条件符合其中任意一个即可。 例如,查询优先级大于等于 3 或者已经完成了的 todo:

Todo = leancloud.Object.extend('Todo')

priority_query = Todo.query
priority_query.greater_than_or_equal_to('priority', 3)

is_complete_query = Todo.query
is_complete_query.equal_to('isComplete', True)

query = leancloud.Query.or_(priority_query, is_complete_query)

使用 OR 查询时,子查询中不能包含 GeoPoint 相关的查询。

AND 查询

使用 AND 查询的效果等同于往 leancloud.Query 添加多个条件。下面的代码构建的查询会查找创建时间在 2016-11-132016-12-02 之间的 todo:

from datetime import datetime

Todo = leancloud.Object.extend('Todo')

start_date_query = Todo.query
start_date_query.greater_than_or_equal_to('createdAt', datetime(2016, 11, 13))

end_date_query = Todo.query
end_date_query.less_than('createdAt', datetime(2016, 12, 3))

query = leancloud.Query.and_(start_date_query, end_date_query)

单独使用 AND 查询跟使用基础查询相比并没有什么不同,不过当查询条件中包含不止一个 OR 查询时,就必须使用 AND 查询:

from datetime import datetime

Todo = leancloud.Object.extend('Todo')

created_at_query = Todo.query
created_at_query.greater_than_or_equal_to('createdAt', datetime(2018, 4, 30))
created_at_query.less_than('createdAt', datetime(2018, 5, 1))

location_query = Todo.query
location_query.does_not_exist('location')

priority_2_query = Todo.query
priority_2_query.equal_to('priority', 2)

priority_3_query = Todo.query
priority_3_query.equal_to('priority', 3)

priority_query = leancloud.Query.or_(priority_2_query, priority_3_query)
time_location_query = leancloud.Query.or_(created_at_query, location_query)
query = leancloud.Query.and_(priority_query, time_location_query)

查询性能优化

影响查询性能的因素很多。特别是当查询结果的数量超过 10 万,查询性能可能会显著下降或出现瓶颈。以下列举一些容易降低性能的查询方式,开发者可以据此进行有针对性的调整和优化,或尽量避免使用。

  • 不等于和不包含查询(无法使用索引)
  • 通配符在前面的字符串查询(无法使用索引)
  • 有条件的 count(需要扫描所有数据)
  • skip 跳过较多的行数(相当于需要先查出被跳过的那些行)
  • 无索引的排序(另外除非复合索引同时覆盖了查询和排序,否则只有其中一个能使用索引)
  • 无索引的查询(另外除非复合索引同时覆盖了所有条件,否则未覆盖到的条件无法使用索引,如果未覆盖的条件区分度较低将会扫描较多的数据)

文件

有时候应用需要存储尺寸较大或结构较为复杂的数据,这类数据不适合用 leancloud.Object 保存,此时文件对象 leancloud.File 便成为了更好的选择。文件对象最常见的用途是保存图片,不过也可以用来保存文档、视频、音乐等其他二进制数据。

构建文件

可以通过字符串构建文件:

from StringIO import StringIO

data = StringIO('LeanCloud')
# resume.txt 是文件名
file = leancloud.File('resume.txt', data)

还可以用 buffer 构建文件:

data = buffer('\x4c\x65\x61\x6e\x43\x6c\x6f\x75\x64')
file = leancloud.File('resume.txt', data)

除此之外,还可以通过 URL 构建文件:

file = leancloud.File.create_with_url('logo.png', 'https://leancloud.cn/assets/imgs/press/Logo%20-%20Blue%20Padding.a60eb2fa.png')

通过 URL 构建文件时,SDK 并不会将原本的文件转储到云端,而是会将文件的物理地址存储为字符串,这样也就不会产生任何文件上传流量。使用其他方式构建的文件会被保存在云端。

云端会根据文件扩展名自动检测文件类型。如果需要的话,也可以手动指定 Content-Type(一般称为 MIME 类型):

file = leancloud.File('resume.txt', StringIO('{"company":"LeanCloud"}'), 'application/json')

与前面提到的方式相比,一个更常见的文件构建方式是从本地路径上传。

with open('/tmp/avatar.jpg', 'rb') as f:
file = leancloud.File('avatar.jpg', f)

这里上传的文件名字叫做 avatar.jpg。需要注意:

  • 每个文件会被分配到一个独一无二的 objectId,所以在一个应用内是允许多个文件重名的。
  • 文件必须有扩展名才能被云端正确地识别出类型。比如说要用 leancloud.File 保存一个 PNG 格式的图像,那么扩展名应为 .png
  • 如果文件没有扩展名,且没有手动指定类型,那么云服务将默认使用 application/octet-stream

如果希望指定文件上传后在云端的路径,可以设置文件的 key 属性。 例如,上传 robots.txt 文件,限制搜索引擎抓取自定义文件域名下的 URL:

with open('/tmp/robots.txt', 'rb') as f:
file = leancloud.File('robots.txt', f)
file.key = 'robots.txt'

出于安全性考虑,指定 key 属性需要使用 masterKey,否则保存时会报错。

保存文件

将文件保存到云端后,便可获得一个永久指向该文件的 URL:

file.save()
print(file.url)

文件上传后,可以在 _File class 中找到。已上传的文件无法再被修改。如果需要修改文件,只能重新上传修改过的文件并取得新的 objectId 和 URL。

已经保存到云端的文件可以关联到 leancloud.Object

Todo = leancloud.Object.extend('Todo')
todo = Todo()
todo.set('title', '买蛋糕')
# attachments 是一个 Array 属性
todo.add('attachments', file)
todo.save()

也可以通过构建 leancloud.Query 进行查询

query = leancloud.Query('_File')

需要注意的是,内部文件(上传到文件服务的文件)的 url 字段是由云端动态生成的,其中涉及切换自定义域名的相关处理逻辑。 因此,通过 url 字段查询文件仅适用于外部文件(直接保存外部 URL 到 _File 表创建的文件),内部文件请改用 key 字段(URL 中的路径)查询。

文件元数据

上传文件时,可以用 metaData 添加额外的属性。文件一旦保存,metaData 便不可再修改。

# 设置元数据
file.metadata['author'] = 'LeanCloud'
file.save()

# 获取全部元数据
metadata = file.metadata
# 获取 author 属性
author = metadata['author']
# 获取文件名
file_name = file.name
# 获取大小(不适用于通过 base64 编码的字符串或者 URL 保存的文件)
size = file.size

图像缩略图

成功保存图像后,除了可以获取指向该文件的 URL 外,还可以获取图像的缩略图 URL,并且可以指定缩略图的宽度和高度:

avatar = leancloud.File.create_without_data('5732df1c1ea4930060ba4642')
avatar.fetch()

thumbnail_url = avatar.get_thumbnail_url(width=100, height=100)

图片最大不超过 20 MB 才可以获取缩略图。

国际版不支持图片缩略图。

删除文件

下面的代码从云端删除一个文件:

file = leancloud.File.create_without_data('552e0a27e4b0643b709e891e')
file.destroy()

默认情况下,文件的删除权限是关闭的,需要进入 云服务控制台 > 数据存储 > 结构化数据 > _File,选择 其他 > 权限设置 > delete 来开启。

GeoPoint

云服务允许你通过将 leancloud.GeoPoint 关联到 leancloud.Object 的方式存储折射真实世界地理位置的经纬坐标,这样做可以让你查询包含一个点附近的坐标的对象。常见的使用场景有「查找附近的用户」和「查找附近的地点」。

要构建一个包含地理位置的对象,首先要构建一个地理位置。下面的代码构建了一个 leancloud.GeoPoint 并将其纬度(latitude)设为 39.9,经度(longitude)设为 116.4

point = leancloud.GeoPoint(39.9, 116.4)

现在可以将这个地理位置存储为一个对象的属性:

todo.set('location', point)

地理位置查询

给定一些含有地理位置的对象,可以从中找出离某一点最近的几个,或者处于某一范围内的几个。要执行这样的查询,可以向普通的 leancloud.Query 添加 near 条件。下面的代码查找 location 属性值离某一点最近的 Todo 对象:

query = leancloud.Query('Todo')
point = leancloud.GeoPoint(39.9, 116.4)
query.near('location', point)

# 限制为 10 条结果
query.limit(10)
todo_list = query.find()

ascendingdescending 这样额外的排序条件会获得比默认的距离排序更高的优先级。

若要限制结果和给定地点之间的距离,可以参考 API 文档中的 within_kilometerswithin_mileswithin_radians 参数。

若要查询在某一矩形范围内的对象,可以用 within_geo_box

withinGeoBox

query = leancloud.Query('Todo')
southwest = leancloud.GeoPoint(30, 115)
northeast = leancloud.GeoPoint(40, 118)
query.within_geo_box('location', southwest, northeast)

GeoPoint 的注意事项

GeoPoint 的经纬度的类型是数字,且经度需在 -180.0 到 180.0 之间,纬度需在 -90.0 到 90.0 之间。 另外,每个对象最多只能有一个类型为 GeoPoint 的属性。

用户

请参阅内建账户指南

角色

随着用户量的增长,你可能会发现相比于为每一名用户单独设置权限,将预先设定好的权限直接分配给一部分用户是更好的选择。为了迎合这种需求,云服务支持基于角色的权限管理。请参阅ACL 权限管理开发指南

子类化

云服务希望设计成能让人尽快上手并使用。你可以通过 leancloud.Object.get() 方法访问所有的数据。但是在很多现有成熟的代码中,子类化能带来更多优点,诸如简洁、可扩展性以及 IDE 提供的代码自动完成的支持等等。子类化不是必须的,你可以将下列代码转化:

import leancloud

student = leancloud.Object.extend("Student")()
student.set('name', '小明')
student.save()

可改写成:

import leancloud

class Student(leancloud.Object):
pass

student = Student()
student.set('name', '小明')
student.save()

子类化 leancloud.Object

要实现子类化,需要下面几个步骤:

  1. 首先声明一个子类继承自 leancloud.Object
  2. 子类化时如果有自定义的构造函数,需要在构造函数中调用父类的构造函数。

下面是实现 Student 子类化的例子:

import leancloud

class Student(leancloud.Object):
pass

student = Student()
student.set('name', 'Tom')
student.save()

访问器、修改器和方法

添加方法到 leancloud.Object 的子类有助于封装类的逻辑。你可以将所有跟子类有关的逻辑放到一个地方,而不是分成多个类来分别处理业务逻辑和存储或转换逻辑。

你可以很容易地为 leancloud.Object 子类添加访问器和修改器。像平常那样声明字段的 gettersetter 方法,但是通过 leancloud.Objectgetset 方法来实现它们。下面是这个例子为 Student 类创建了一个 content 的字段:

import leancloud

class Student(leancloud.Object):
@property
def content(self):
# 可以使用 property 装饰器,方便获取属性
return self.get('content')

@content.setter
def content(self, value):
# 同样可以给对象的 content 增加 setter
return self.set('content', value)

现在你就可以使用 student.content 方法来访问 content 字段,并通过 student.content = "blah blah blah" 来修改它,并且可以通过 IDE 或者 linter 在运行前就可以发现类型错误。

如果你不仅需要一个简单的访问器,而是有更复杂的逻辑,你可以在子类化时定义和实现自己的方法。

初始化子类

你可以使用自定义的构造函数来创建子类对象。子类必须定义一个公开的默认构造函数,并且不修改任何父类 leancloud.Object 中的字段,这个默认构造函数将会被 SDK 使用来创建子类的强类型的对象。

要创建一个到现有对象的引用,可以使用 leancloud.Object.create_without_data()

import leancloud

Student = leancloud.Object.extend('Student')
student = Student.create_without_data('573a8459df0eea005e6b711c')
student.fetch()

查询子类

你可以通过对象的 query 属性获取特定的子类的查询对象。下面的例子就查询了用户发表的所有微博列表:

import leancloud

query = leancloud.Query('Student')
user_name = leancloud.User.get_current().get_username()
query.equal_to('publeancloud.User', user_name)
student_list = query.find()

for student in student_list:
# 你的逻辑
pass

User 的子类化

leancloud.User 作为 leancloud.Object 的子类,同样允许子类化,你可以定义自己的 User 对象。需要继承 User,并且将子类的 _class_name 设为 '_User'

import leancloud

class MyUser(leancloud.User):
def __init__(self):
leancloud.User.__init__(self)
self._class_name = '_User' # 这里要指定 _class_name 为 _User

def set_nickname(self, name):
self.set('nick_name', name)

def get_nickname(self):
return self.get('nick_name')

全文搜索

全文搜索是一个针对应用数据进行全局搜索的接口,它基于搜索引擎构建,提供更强大的搜索功能。要深入了解其用法和阅读示例代码,请阅读全文搜索指南