目录
- 对象
- 1. 保存到云
- 2. 获取云对象 (查询)
- 3. 云同步本地对象
- 4. 更新云对象
- 5.更新云对象中的数组,原子操作
- 6. 删除云对象
- 7. 批量 [存(改),更新,删] 云对象
- 8. 后台执行,无网络等待执行
- 9. 一篇文章,多个评论
- 10. 转Json和恢复
- 查询
- =
- !=
- >=
- <=
- and
- skip
- limit
- First
- 排序
- 同一个查询添加多个排序规则
- 存在(对象的属性)
- 不存在(对象的属性)
- 指定返回的属性
- 字符前缀等于
- 字符串包含
- 字符串正则表达式
- 数组中包含
- 数组长度
- or
- and
- 文章下的所有评论
- 所有文章包含(不包含)图片的评论
- 获取评论中的文章
- 结果总数
- 影响查询性能的事件
- LiveQuery
- 订阅,事件处理,取消订阅
- 网络状态
- 注意事项
- 文件
- 构建文件
- 保存文件
- 作为对象的属性
- 查询
- 获取对象中的文件数组
- 上传文件进度
- 文件元数据
- 图片略缩图
- 文件下载
- 文件删除
- GeoPoint(地理点,地理位置,经纬度点)
- 创建一个地理点
- 作为对象的属性
- 地理点接近的对象查询
- 限制距离
- 查询地理点在某一矩形范围内的对象
- 用户
- 用户名+密码注册
- 手机号注册
- 用户名+密码登录
- 邮箱号+密码登录
- 手机号+密码登录
- 手机验证码登录
- 测试手机号注册和登录
- 单设备账号登录
- 多次登录失败,账号锁定
- 验证邮箱
- 验证手机号
- 绑定、修改手机号之前先验证
- 是否已有登录的用户
- 退出账号
- 使用会话令牌登录
- 重置密码(电子邮箱)
- 重置密码(手机号)
- 搜索用户
- 用户作为对象的属性
- 用户对象安全
- 第三方登录
- 自动验证第三方平台授权信息是否有效
- 绑定第三方账号
- 解除第三方账号绑定
- 第三方登录时补充完整的用户信息
- 匿名用户
对象 1. 保存到云
禁止用户创建Class:控制台 > 数据存储 > 服务设置
// 创建一个云对象
val todo = LCObject("Todo")
// 给对象中的属性赋值
todo.put("title", "工程师周会")
todo.put("content", "周二两点,全体成员")
todo
// 在其他线程将对象保存到云端
.saveInBackground()
// 回调
.subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(todo: LCObject) {}
override fun onError(throwable: Throwable) {}
override fun onComplete() {}
})
2. 获取云对象 (查询)
// 实例云对象查询类 val query = LCQuery3. 云同步本地对象("Todo") // 通过Id获取云对象 query .getInBackground("582570f38ac247004f39c24b") .subscribe(object : Observer { override fun onSubscribe(disposable: Disposable) {} override fun onNext(todo: LCObject) {} override fun onError(throwable: Throwable) {} override fun onComplete() {} })
// 创建一个具有Id值的云对象
val todo = LCObject.createWithoutData("Todo", "582570f38ac247004f39c24b")
// 指定被同步的属性,不填则全部刷新
val keys = "priority, location"
// 同步对象的属性
todo
.fetchInBackground(keys)
.subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCObject) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
// fetchIfNeededInBackground 替换 fetchInBackground 云端没有,则LCObject = 本地LCObject
4. 更新云对象
// 创建一个具有Id值的云对象
val todo = LCObject.createWithoutData("Todo", "582570f38ac247004f39c24b")
// 放入需要修改的属性值
todo.put("content", "这周周会改到周三下午三点。")
// 在属性值的基础上增加,原子操作
account.increment("balance", -100)
// 特定属性值是否应该更新
val option = LCSaveOption()
// 要求balance -100 之前必须 >=100
option.query(
LCQuery("Account").whereGreaterThanOrEqualTo("balance", 100)
)
// 更新对象后,获取最新云对象
option.setFetchWhenSave(true)
// 更新对象的属性
todo.saveInBackground(option).subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCObject) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
5.更新云对象中的数组,原子操作
// 创建一个云对象
val todo = LCObject("Todo")
// 添加到数组末尾
todo.add("alarms", "")
// 添加的对象必须在数组中唯一,插入随机位置
todo.addUnique("alarms", "")
// 删除传入的数组中的所有对象
todo.removeAll("alarms", listOf())
// 更新对象的属性
account.saveInBackground().subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(account: LCObject) {}
override fun onError(throwable: Throwable) {}
override fun onComplete() {}
})
6. 删除云对象
// 创建一个具有Id值的云对象
val todo = LCObject.createWithoutData("Todo", "582570f38ac247004f39c24b")
// 删除对象
todo.deleteInBackground().subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
7. 批量 [存(改),更新,删] 云对象
// 创建一个查询对象 val query = LCQuery8. 后台执行,无网络等待执行("Todo") // 获取此类的所有对象 query.findInBackground().subscribe(object : Observer > { override fun onSubscribe(disposable: Disposable) {} override fun onNext(todos: List
) { for (todo in todos) { todo.put("isComplete", true) } // 一次操作多个对象 // saveAll 和 fetchAll 实际与调用多次save 和 fetch 计费上无区别 // deleteAll 与 调用多次 delete 有区别,deleteAll仅计费一次操作 // 计费:接口调用次数计费 LCObject.saveAll(todos) } override fun onError(throwable: Throwable) {} override fun onComplete() {} })
val todo = LCObject("Todo")
// 当没有网络时,会等待网络连接时再执行,即便应用被关闭了,下次打开时,会继续尝试执行,按调用顺序执行
todo.saveEventually()
todo.deleteEventually()
9. 一篇文章,多个评论
一对一:一个表的row储存另一个表的rowid(关系型数据库)
一对一:一个类的对象属性储存另一个类的对象指针(LeanCloud / MongoDB)
一对多:一个表的rowid被多个表的row储存(关系型数据库)
一对多:一个类的对象储存另一个类的对象指针数组(LeanCloud / MongoDB)
多对多:一个表的row储存多个表的rowid(关系型数据库)
多对多:一个类的对象储存多个类的对象指针(LeanCloud / MongoDB)
// 一篇文章
LCObject post = LCObject.createWithoutData("Post", "57328ca079bc44005c2472d0")
// 一条评论
val comment = LCObject("Comment")
// 被指向的对象,被储存为 Pointer(指针)
comment.put("parent", post)
comment.put("content", "当然是肯德基啦!")
// 保存评论
comment.saveEventually()
10. 转Json和恢复
val todo = LCObject("Todo")
// 转json
val serializedString = todo.toString()
// json转对象
val deserializedObject = LCObject.parseLCObject(serializedString)
// 保存到云端
deserializedObject.saveEventually()
查询
一次获取多个符合特定条件的 LCObject
// 实例云对象查询类 val query = LCQuery=("Student") // 查询返回云对象集合 query.findInBackground().subscribe(object : Observer ?> { override fun onSubscribe(d: Disposable) {} override fun onNext(t: List
) {} override fun onError(e: Throwable) {} override fun onComplete() {} })
// 条件:属性等于
query.whereEqualTo("lastName", "Smith")
!=
query.whereNotEqualTo("firstName", "Jack")
>=
query.whereLessThan("age", 18)
query.whereLessThanOrEqualTo("age", 18)
<=
query.whereGreaterThan("age", 18)
query.whereGreaterThanOrEqualTo("age", 18)
and
// 等于
query.whereEqualTo("firstName", "Jack")
// 大于
query.whereGreaterThan("age", 18)
skip
// 跳过前20条结果 // skip的值越高,查询所需的时间就越长 query.skip(20) // 可以通过设置 createdAt 或 updatedAt 的范围来实现更高效的翻页,因为它们都自带索引。 // 同理,也可以通过设置自增字段的范围来实现翻页。limit
// 返回数量,默认 100,最大1000 query.limit(10)First
query.firstInBackground.subscribe(object : Observer排序{ override fun onSubscribe(d: Disposable) {} override fun onNext(t: LCObject) {} override fun onError(e: Throwable) {} override fun onComplete() {} })
// 按 createdAt 升序排列
query.orderByAscending("createdAt")
// 按 createdAt 降序排列
query.orderByDescending("createdAt")
同一个查询添加多个排序规则
query.addAscendingOrder("priority")
query.addDescendingOrder("createdAt")
存在(对象的属性)
query.whereExists("images");
不存在(对象的属性)
// 查找不存在 "images" 属性的对象
query.whereDoesNotExist("images")
指定返回的属性
// 实例云对象查询类 val query = LCQuery字符前缀等于("Todo") // 需要返回的字段 -notes 表示不返回 query.selectKeys(listOf("title", "content", "-notes")) query.firstInBackground.subscribe(object : Observer { override fun onSubscribe(disposable: Disposable) {} override fun onNext(todo: LCObject) { val title = todo.getString("title") // √ val content = todo.getString("content") // √ val notes = todo.getString("notes") // 会报错 } override fun onError(throwable: Throwable) {} override fun onComplete() {} })
// 利用索引带来的优势 区分大小写
query.whereStartsWith("title", "lunch")
字符串包含
// 无法利用索引(遍历所有对象) 区分大小写
query.whereContains("title", "lunch")
字符串正则表达式
// 无法利用索引(遍历所有对象) 不区分大小写
query.whereMatches("title", "^((?!ticket).)*$", "i")
数组中包含
// 一个对象
query.whereEqualTo("tags", "工作")
// 另一个数组
query.whereContainsAll("tags", Arrays.asList("工作", "销售", "会议"))
数组长度
query.whereSizeEqual("tags", 3)
or
- 单个属性
// 属性 in 数组
query.whereContainedIn("priority", Arrays.asList(1, 2))
// 属性 !in 数组
query.whereNotContainedIn("priority", Arrays.asList(1, 2))
- 多个属性
// 属性1 = 值1 or 属性2 = 值2 val priorityQuery = LCQueryand("Todo") priorityQuery.whereGreaterThanOrEqualTo("priority", 3) val isCompleteQuery = LCQuery ("Todo") isCompleteQuery.whereEqualTo("isComplete", true) val query = LCQuery.or(listOf(priorityQuery, isCompleteQuery))
// 用于连接 or 查询 val priorityQuery: LCQuery文章下的所有评论= LCQuery.or(listOf(priority2Query, priority3Query)) val timeLocationQuery: LCQuery = LCQuery.or(listOf(locationQuery, createdAtQuery)) val query = LCQuery.and(listOf(priorityQuery, timeLocationQuery))
// 创建一个具有Id值的云对象
val post = LCObject.createWithoutData("Post", "57328ca079bc44005c2472d0")
// 实例云对象查询类
val query = LCQuery("Comment")
// 查询条件
query.whereEqualTo("post", post)
// 查询返回云对象集合
query.findInBackground().subscribe(object : Observer> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: List) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
所有文章包含(不包含)图片的评论
// 实例云对象查询类 val innerQuery = LCQuery获取评论中的文章() // 条件 innerQuery.whereExists("image") // 实例云对象查询类 val query = LCQuery ("Comment") // 条件:包含 query.whereMatchesQuery("post", innerQuery) // 条件:不包含 query.whereDoesNotMatchQuery("post", innerQuery)
val query = LCQuery结果总数("Comment") query.orderByDescending("createdAt") query.limit(10) // 获取文章 文章是一个云对象 query.include("post") query.findInBackground().subscribe(object : Observer > { override fun onSubscribe(disposable: Disposable) {} override fun onNext(comments: List
) { for (comment in comments) { val post = comment.getLCObject ("post") } } override fun onError(throwable: Throwable) {} override fun onComplete() {} })
query.countInBackground().subscribe(object : Observer影响查询性能的事件{ override fun onSubscribe(disposable: Disposable) {} override fun onNext(count: Int) {} override fun onError(throwable: Throwable) {} override fun onComplete() {} })
- 不等于和不包含查询(无法使用索引)
- 通配符在前面的字符串查询(无法使用索引)
- 有条件的 count(需要扫描所有数据)
- skip 跳过较多的行数(相当于需要先查出被跳过的那些行)
- 无索引的排序(另外除非复合索引同时覆盖了查询和排序,否则只有其中一个能使用索引)
- 无索引的查询(另外除非复合索引同时覆盖了所有条件,否则未覆盖到的条件无法使用索引,如果未覆盖的条件区分度较低将会扫描较多的数据)
LiveQuery 订阅,事件处理,取消订阅
// 实例云对象查询类 val query = LCQuery网络状态("Todo") // 实例实时查询对象 val liveQuery = LCLiveQuery.initWithQuery(query) // 云对象实时更新时的事件处理 liveQuery.setEventHandler(object : LCLiveQueryEventHandler() { // 当有新的满足 LCQuery 查询条件的 LCObject 被创建时 override fun onObjectCreated(newTodo: LCObject) {} // 当有满足 LCQuery 查询条件的 LCObject 被更新时 override fun onObjectUpdated(LCObject: LCObject?, updateKeyList: MutableList ?) {} // 当一个已存在的、原本不符合 LCQuery 查询条件的 LCObject 发生更新,且更新后符合查询条件时 override fun onObjectEnter(LCObject: LCObject?, updateKeyList: MutableList ?) {} // 当一个已存在的、原本符合 LCQuery 查询条件的 LCObject 发生更新,且更新后不符合查询条件时 override fun onObjectLeave(LCObject: LCObject?, updateKeyList: MutableList ?) {} // 当一个已存在的、原本符合 LCQuery 查询条件的 LCObject 被删除时 override fun onObjectDeleted(objectId: String?) {} }) // 开始订阅 liveQuery.subscribeInBackground(object : LCLiveQuerySubscribeCallback() { override fun done(e: LCException) {} }) // 取消订阅 liveQuery.unsubscribeInBackground(object : LCLiveQuerySubscribeCallback() { override fun done(e: LCException) { } })
网络异常,SDK 会自动重新订阅,数据变更会继续推送到客户端
LCLiveQuery.setConnectionHandler(object : LCLiveQueryConnectionHandler {
// 网络连接
override fun onConnectionOpen() {}
// 网络断开
override fun onConnectionClose() {}
// 网络错误
override fun onConnectionError(code: Int, reason: String) {}
})
注意事项
文件 构建文件LiveQuery 仅用于对查询结果的变化进行监听
不适用于搭建聊天室功能
文件名可以重复,id 不同
文件有扩展名可以被云端正确地识别出类型
若文件没有扩展名,且没有手动指定类型,云服务将默认使用 application/octet-stream
val meta: MutableMap保存文件= HashMap() // 手动指定文件类型(媒体类型) meta["mime_type"] = "image/png" // 实例云文件对象 val file = LCFile("resume.txt", "Hello World".toByteArray()) // 实例云文件对象,通过链接提供内容, // 并不是转储原本的文件到云端,仅存储文件的物理地址 val file = LCFile( "logo.png", "https://leancloud.cn/assets/imgs/press/Logo%20-%20Blue%20Padding.a60eb2fa.png", meta ) // 实例云文件对象,使用本地文件提供内容 val file = LCFile.withAbsoluteLocalPath("avatar.jpg", "/tmp/avatar.jpg")
将文件保存到云端后,便可获得一个永久指向该文件的 URL
文件上传后,可以在 _File class 中找到
无法修改已上传的文件,只能重新上传修改过的文件并取得新的 objectId 和 URL。
// 将文件保存到云端 file.saveInBackground().subscribe(object : Observer作为对象的属性{ override fun onSubscribe(disposable: Disposable) {} override fun onNext(file: LCFile) { println("文件保存完成。URL:" + file.url) } override fun onError(throwable: Throwable) { // 保存失败,可能是文件无法被读取,或者上传过程中出现问题 } override fun onComplete() {} })
val todo = LCObject("Todo")
todo.put("title", "买蛋糕")
// attachments 是一个 Array 属性
// 已经保存到云端的文件
todo.add("attachments", file)
todo.save()
查询
分为外部文件和内部文件
外部文件:仅储存原文件链接
内部文件:储存文件,并生成链接
val query = LCQuery获取对象中的文件数组("_File")
// 获取对象中的文件数组
query.include("attachments")
query.findInBackground().subscribe(object : Observer> {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(todos: List) {
for (todo in todos) {
todo?.getList("attachments")
}
}
override fun onError(throwable: Throwable) {}
override fun onComplete() {}
})
上传文件进度
file.saveInBackground(object : ProgressCallback() {
override fun done(percent: Int) {
// percent 是一个 0 到 100 之间的整数,表示上传进度
}
})
文件元数据
上传后无法修改
file.addmetaData("author", "LeanCloud")
file.saveInBackground().subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(file: LCFile) {
// 获取 author 属性
val author = file.getmetaData("author") as String
// 获取文件名
val fileName = file.name
// 获取大小(不适用于通过 base64 编码的字符串或者 URL 保存的文件)
val size = file.size
}
override fun onError(throwable: Throwable) {}
override fun onComplete() {}
})
图片略缩图
val file = LCFile("test.jpg", "文件-url", HashMap())
// 原图不能超过20MB
file.getThumbnailUrl(true, 100, 100)
文件下载
也可以得到url后,使用其他库下载
// 一 file.getDataInBackground().subscribe(object : Observer文件删除{ override fun onSubscribe(d: Disposable) {} override fun onNext(bytes: ByteArray) { Log.d("file data length: " + bytes.size) } override fun onError(e: Throwable) { Log.d("failed to get data. cause: " + e.message) } override fun onComplete() {} }) // 二 file.getDataStreamInBackground().subscribe(object : Observer { override fun onSubscribe(d: Disposable) {} override fun onNext(inputStream: InputStream) { try { val buffer = ByteArray(102400) val read: Int = inputStream.read(buffer) Log.d("file data length: $read") inputStream.close() } catch (ex: Exception) { ex.printStackTrace() } } override fun onError(e: Throwable) { Log.d("failed to get data. cause: " + e.message) } override fun onComplete() {} })
允许用户删除文件:控制台 > 数据存储 > 结构化数据 > _File > 权限
val file = LCObject.createWithoutData("_File", "552e0a27e4b0643b709e891e")
file.deleteInBackground().subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
GeoPoint(地理点,地理位置,经纬度点)
创建一个地理点GeoPoint 的经纬度的类型是数字
经度需在 -180.0 到 180.0 之间,
纬度需在 -90.0 到 90.0 之间。
每个对象最多只能有一个类型为 GeoPoint 的属性。
LCGeoPoint point = new LCGeoPoint(39.9, 116.4)作为对象的属性
todo.put("location", point)
地理点接近的对象查询
val query = LCQuery限制距离("Todo") val point = LCGeoPoint(39.9, 116.4) query.whereNear("location", point) // 限制为 10 条结果 query.limit(10) query.findInBackground().subscribe(object : Observer ?> { override fun onSubscribe(disposable: Disposable) {} fun onNext(todos: List
?) { // todos 是包含满足条件的 Todo 对象的数组 } override fun onError(throwable: Throwable) {} override fun onComplete() {} })
查询地理点在某一矩形范围内的对象若要限制结果和给定地点之间的距离,
可以参考 API 文档中的 whereWithinKilometers、whereWithinMiles 和 whereWithinRadians 参数。
val query = LCQuery用户("Todo") val southwest = LCGeoPoint(30, 115) val northeast = LCGeoPoint(40, 118) query.whereWithinGeoBox("location", southwest, northeast)
LCUser 是 LCObject 的子类
LCUser 相比普通的 LCObject 多出以下属性:
- username:用户名
- password:密码
- email:电子邮箱
- emailVerified:是否已验证电子邮箱
- mobilePhoneNumber:手机号
- mobilePhoneVerified:是否已验证手机号
202: 用户名已存在
203 或 214: 手机或邮箱已存在
注册时:密码是以明文方式通过 HTTPS 加密传输给云端, 云端以密文存储密码
// 实例云用户对象
val user = LCUser()
// 输入账号和密码 等同于 user.put("username", "Tom")
user.username = "Tom"
user.password = "cat!@#123"
// 输入邮箱和手机号 可选
user.email = "tom@leancloud.rocks"
user.mobilePhoneNumber = "+8618200008888"
// 设置其他属性的方法跟 LCObject 一样
user.put("gender", "secret")
// 注册
user.signUpInBackground().subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(user: LCUser) {
// 注册成功
}
override fun onError(throwable: Throwable) {
// 注册失败
}
override fun onComplete() {}
})
手机号注册
username 将与 mobilePhoneNumber 相同
password 会由云端随机生成
手机号格式: +[国家代码][手机号]
示例:+8618200008888
- 获取验证码
// 短信模板 可以传null 使用默认模板
val option = LCSMSOption()
// 设置短信签名名称
option.setSignatureName("sign_name")
LCSMS
// 短信格式
.requestSMSCodeInBackground("+8618200008888", option)
// 向手机发送6位验证码
.safeSubscribe(object : Observer{
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
- 完成注册
LCUser.signUpOrLoginByMobilePhoneInBackground("+8618200008888", "123456")
.subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(user: LCUser) {
// 注册成功
}
override fun onError(throwable: Throwable) {
// 验证码不正确
}
override fun onComplete() {}
})
用户名+密码登录
LCUser.logIn("Tom", "cat!@#123").subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCUser) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
邮箱号+密码登录
LCUser.loginByEmail("tom@leancloud.rocks", "cat!@#123").subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCUser) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
手机号+密码登录
控制台 > 内建账户
未验证手机号码的用户,禁止使用手机号登录
已验证手机号码的用户,允许以短信验证码登录
LCUser.loginByMobilePhoneNumber("+8618200008888", "cat!@#123")
.subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCUser) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
手机验证码登录
- 发送验证码
LCUser.requestLoginSmsCodeInBackground("+8618200008888")
.subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
- 完成登录
LCUser.signUpOrLoginByMobilePhoneInBackground("+8618200008888", "123456")
.subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(user: LCUser) {
// 登录成功
}
override fun onError(throwable: Throwable) {
// 验证码不正确
}
override fun onComplete() {}
})
测试手机号注册和登录
控制台 > 短信 > 设置 > 测试手机号
在这里插入代码片单设备账号登录
新建一个专门用于记录以下信息的class
用户登录信息
当前设备信息
在新设备上登录时
该用户对应的设备更新为该设备
多次登录失败,账号锁定另一台设备上打开客户端时
检查该设备是否与云端保存的一致。
若不一致,则将用户 登出
验证邮箱如果在 15 分钟内,同一个用户登录失败的次数大于 6 次
15 分钟之后,才能继续尝试
用户点击邮件内的链接后,emailVerified 会变为 true。
如果用户的 email 属性为空,则该属性永远不会为 true
LCUser.requestEmailVerifyInBackground("tom@leancloud.rocks").subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
验证手机号
- 发送验证码
LCUser.requestMobilePhoneVerifyInBackground("+8618200008888").subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
- 完成验证
LCUser.verifyMobilePhoneInBackground("123456").subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
绑定、修改手机号之前先验证
- 发送验证码
// 短信模板 可以传null 使用默认模板
val option = LCSMSOption()
// 设置短信签名名称
option.setSignatureName("sign_name")
// 发送验证码
LCUser.requestSMSCodeForUpdatingPhoneNumberInBackground("+8618200008888", option).subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
- 验证和绑定手机号
LCUser.verifySMSCodeForUpdatingPhoneNumberInBackground("123456", "+8618200008888")
.subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {
// 更新本地数据
val currentUser = LCUser.getCurrentUser()
currentUser.mobilePhoneNumber = "+8618200008888"
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
是否已有登录的用户
val currentUser = LCUser.getCurrentUser()
if (currentUser != null) {
// 跳到首页
} else {
// 显示注册或登录页面
}
退出账号
LCUser.logOut()使用会话令牌登录
根据以前缓存的session token 登录,多账户切换
WebView 需要知道当前登录的用户
在服务端登录后,返回 session token 给客户端,客户端根据返回的 session token 登录
// LCUser.getCurrentUser().getSessionToken()
LCUser.becomeWithSessionTokenInBackground("anmlwi96s381m6ca7o7266pzf")
.subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(user: LCUser) {
// 修改 currentUser
LCUser.changeCurrentUser(user, true)
}
override fun onError(throwable: Throwable) {
// session token 无效
}
override fun onComplete() {}
})
验证会话令牌是否有效
控制台 > 内建账户 > 设置
勾选了 密码修改后,强制客户端重新登录
那么当一个用户修改密码后,该用户的 session token 会被重置
boolean authenticated = LCUser.getCurrentUser().isAuthenticated();
if (authenticated) {
// session token 有效
} else {
// session token 无效
}
重置密码(电子邮箱)
- 发送密码重置邮件
LCUser.requestPasswordResetInBackground("tom@leancloud.rocks").subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
重置密码(手机号)
- 发送验证码
LCUser.requestPasswordResetBySmsCodeInBackground("+8618200008888").subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
- 完成密码重置
// 输入验证码和新密码, 完成密码重置
LCUser.resetPasswordBySmsCodeInBackground("123456", "cat!@#`在这里插入代码片`123").subscribe(
object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: LCNull) {}
override fun onError(e: Throwable) {}
override fun onComplete() {}
}
)
搜索用户
val userQuery = LCUser.getQuery()用户作为对象的属性
val author = LCUser.getCurrentUser()
val book = LCObject("Book")
book.put("title", "我的第五本书")
book.put("author", author)
用户对象安全
用户对象的密码只能在注册的时候进行设置,
之后如需修改,只能通过 重置密码
- 可修改用户对象的属性
LCUser.logIn("Tom", "cat!@#123").subscribe(object : Observer {
override fun onSubscribe(disposable: Disposable) {}
override fun onNext(user: LCUser) {
// 试图修改用户名
user.put("username", "Jerry")
// 密码已被加密,这样做会获取到空字符串
val password = user.getString("password")
// 可以执行,因为用户已鉴权
user.save()
}
override fun onError(throwable: Throwable) {}
override fun onComplete() {}
})
// LCUser.signUpInBackground
// LCUser.getCurrentUser()
- 不可修改用户对象属性
// 绕过鉴权直接获取用户 val query = LCQuery第三方登录("_User") query.getInBackground(user.objectId).subscribe(object : Observer { override fun onSubscribe(disposable: Disposable) {} override fun onNext(unauthenticatedUser: LCUser) { unauthenticatedUser.put("username", "Toodle") // 会出错,因为用户未鉴权 unauthenticatedUser.save() // isAuthenticated = false unauthenticatedUser.isAuthenticated() } override fun onError(throwable: Throwable) {} override fun onComplete() {} })
如果已绑定账号,则返回 200 OK 状态码
如果未绑定账号,则返回 201 Created 状态码,并创建新用户
云端首先查找(_User 表),是否存在 authData.weixin.openid = “OPENID” 的账户
如果存在,则返回此有账户
- 微信登录
// 第三方平台的授权信息 val thirdPartyData: MutableMap自动验证第三方平台授权信息是否有效= HashMap() // 必须 thirdPartyData["expires_in"] = 7200 thirdPartyData["openid"] = "OPENID" thirdPartyData["access_token"] = "ACCESS_TOKEN" //可选 thirdPartyData["refresh_token"] = "REFRESH_TOKEN" thirdPartyData["scope"] = "SCOPE" // 如果不存在账号,则失败,或者 新建账号 val failonNotExist = true LCUser.loginWithAuthData(thirdPartyData, "weixin", failOnNotExist).subscribe( object : Observer { override fun onSubscribe(disposable: Disposable) {} override fun onNext(avUser: LCUser) { println("成功登录") } override fun onError(throwable: Throwable) { println("尝试使用第三方账号登录,发生错误。") } override fun onComplete() {} } )
绑定第三方账号控制台 > 内建账户 > 设置
第三方登录时,验证用户 AccessToken 合法性。
第三方集成 > 应用ID …
LCUser.getCurrentUser().associateWithAuthData(weixinData, "weixin").subscribe( object : Observer解除第三方账号绑定{ override fun onSubscribe(d: Disposable) {} override fun onNext(avUser: LCUser) { println("绑定成功") } override fun onError(e: Throwable) { println("绑定失败:" + e.message) } override fun onComplete() {} })
LCUser.currentUser().dissociateWithAuthData("weixin").subscribe(object : Observer {
override fun onSubscribe(d: Disposable) {}
override fun onNext(avUser: LCUser) {
println("解绑成功")
}
override fun onError(e: Throwable) {
println("解绑失败:" + e.message)
}
override fun onComplete() {}
})
第三方登录时补充完整的用户信息
val user = LCUser() user.username = "Tom" user.mobilePhoneNumber = "+8618200008888" user.loginWithAuthData(thirdPartyData, "weixin").subscribe(object : Observer匿名用户{ override fun onSubscribe(d: Disposable) {} override fun onNext(avUser: LCUser) { println("登录成功") } override fun onError(e: Throwable) { println("登录失败:" + e.message) } override fun onComplete() {} })
如果匿名用户未能在登出前转化为普通用户,那么该用户将无法再次登录同一账户,且之前产生的数据也无法被取回。
- 匿名登录
LCUser.logInAnonymously().subscribe(object : Observer{ override fun onSubscribe(disposable: Disposable) {} override fun onNext(user: LCUser) { // user 是新的匿名用户 } override fun onError(throwable: Throwable) {} override fun onComplete() {} })
- 匿名转注册
// currentUser 是个匿名用户 val currentUser = LCUser.getCurrentUser() currentUser.username = "Tom" currentUser.password = "cat!@#123" currentUser.signUpInBackground().subscribe(object : Observer{ override fun onSubscribe(disposable: Disposable) {} override fun onNext(user: LCUser) { // currentUser 已经转化为普通用户 } override fun onError(throwable: Throwable) { // 注册失败(通常是因为用户名已被使用) } override fun onComplete() {} })
- 检查当前用户是否为匿名用户
LCUser currentUser = LCUser.getCurrentUser();
if (currentUser.isAnonymous()) {
// currentUser 是匿名用户
} else {
// currentUser 不是匿名用户
}



