栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

android LeanCloud 数据存储

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

android LeanCloud 数据存储


目录
  • 对象
    • 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 = LCQuery("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() {}
})
3. 云同步本地对象
// 创建一个具有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 = LCQuery("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() {}
})
8. 后台执行,无网络等待执行
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 = LCQuery("Todo")
priorityQuery.whereGreaterThanOrEqualTo("priority", 3)

val isCompleteQuery = LCQuery("Todo")
isCompleteQuery.whereEqualTo("isComplete", true)

val query = LCQuery.or(listOf(priorityQuery, isCompleteQuery))
and
// 用于连接 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 不是匿名用户
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/354405.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号