开发云函数调用云函数
开发云函数
新建项目和应用,开通云函数服务(AGC)
开通云函数服务
使用端云一体化模板创建应用 (DevEco Studio)
新建云函数(DevEcoStudio) 】
编写云函数代码(DevEco Studio)
部署云函数(DevEco Studio)
配置和测试(AGC)
端侧调用云函数
添加依赖
初始化AGConnect
ArkTS 界面开发
ArkTS 调用云函数
云函数,要在真机或者模拟器才能用
云函数开发细节
传参问题
端侧输入姓名,云测根据传来的参数,进行处理,告诉端侧显示不同的欢迎词
端侧
云测
我们要知道端侧传过来什么,就要知道event对象里有什么
查文档
为了程序健壮,我们可以把先判断有没有body,没有怎么处理,或者捕获异常
环境变量
如果我有一些配置项,那么他们容易改变,那我不应该把它写死在程序代码之中,而是把这些配置项呢,给他以环境变量的方式提供
这样就能保证配置项将来改动了,代码不用动
env系统提供的和我们将来可以自定义环境变量
你可以点击这个新增变量来输入变量名变量值
一定要点击保存它才能生效啊
云函数如果它重新部署的话
那么这个上次配置的环境变量会被清空
所以呢我们先去改动一下云函数的代码,把它重新部署之后,然后咱们回过头来再去配置这个环境变量好
流量治理
负载均衡
云函数的一个好处就是它是按量计费,如果你对这个原函数啊并没有使用,那它是不收费的
云函数运行之前,它会把它部署到一个虚拟机实例上
我已经有一段时间没有去调用我的hello,他就把我那个实例销毁了这个时候是不计费的
那将来我对这个原函数一旦要产生调用,它才会动态的创建一个实例,并且把原函数部署上去,这时候才会产生费用啊
如果这个并发量比较大,那云函数这边它是如何去分配,请求到这每个实例的
这就涉及到了负载均衡
重试
云函数的调用是有可能超时的
那么如果你不希望这个函数执行一次就失败,你还想救一下它
我们可以通过流量治理中有一个重视的功能,在函数超时失败之后呢,可以重新发起一次对云函数的调用,如果这次调用能成功,那我们就不用返回失败的信息,还是可以返回成功的信息啊
重试功能呢其实它也分了三种策略
第一种重策略叫做zero
它的意思呢就是说一旦函数发现了它调用失败,那我就立刻发起重视,它中间不会等待
第二种从事策略叫做constant
当你的函数调用失败了之后,他不会说立刻去发起这个重试,他要间隔两秒之后再发起重试
第三种从策略呢叫做jittered
它其实跟我们前面第二种啊比较像,
都是在每次重试之间会有一个时间间隔,不过它的时间间隔不像我们constant,它的时间间隔是一个常量的时间值,它是一个变化的时间值
每次这个时间间隔是以指数方式增长的啊
zero
constant
jittered
熔断
就跟我们日常生活中的保险丝的作用有点像,保险丝呢它在大电流来了以后,保险丝是不是就烧断了
流量治理中的熔断,它的目的也是类似的,比如说我的云函数,它的调用很多次都出错了,那我继续提供原函数服务已经没有意义了
这时候呢我就采用熔断,熔断一旦发生,那么原函数呢就暂停对外的服务了,也就是这个原函数不可用了,
但是原函数不可用是避免了更大
比如说像雪崩事故的发生好
这是熔断的目的啊
它的配置项其实就这么三项
刚开始这个开关没有开的时候,那函数呢在任意时刻都可以对外提供服务
如果我们把这个熔断开关打开,那它就会在满足了某些条件之后触发熔断,一旦函数熔断发生,那么这个函数就不能对外提供服务了
认证服务
开通认证服务
使用认证组件进行认证
第一步登录页面
第二步,登录成功后
至此整个登录流程跑通了
认证流程(1) -上报认证数据
认证凭据,根据你的认证方式不同而不同
比如说呢我们之前用的是手机认证
那手机验证的话,当我点击登录按钮之后,是不是让你输入手机号和验证码,那这个手机号和验证码
就是我们手机认证方式下它的认证凭据
那如果你采用的是邮箱认证呢
那这个认证据就是你的邮件地址和
邮件里收到的认证码
第一次使用认证服务那服务器这边是不是应该还没有这个用户信息
服务器就创建你的服务信息
认证流程(2) -获取用户信息
自行实现登录需求操作
@Entry
@Component
struct MyLoginCustom {
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
Button('获取验证码')
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
}
.width('100%')
}
.height('100%')
}
}
自行实现登录倒计时初步实现、
@Entry
@Component
struct MyLoginCustom {
@State countDown: number = 10
intervalId: number = 0
@State verifyCodeButtonText: string = '获取验证码'
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
Button(this.verifyCodeButtonText)
.onClick(()=> {
this.intervalId = setInterval(() => {
this.verifyCodeButtonText = `${this.countDown} s`
if(this.countDown<0){
clearInterval(this.intervalId)
this.countDown = 10
this.intervalId = 0
this.verifyCodeButtonText = '获取验证码'
return
}
this.countDown--
},1000)
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
}
.width('100%')
}
.height('100%')
}
}
自行实现登录倒计时细节调整
@Entry
@Component
struct MyLoginCustom {
@State countDown: number = 10
intervalId: number = 0
@State verifyCodeButtonText: string = '获取验证码'
@State verifyCodeButtonEnable: boolean = true
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
Button(this.verifyCodeButtonText)
.enabled(this.verifyCodeButtonEnable) // 当点击了,之后按钮不可用
.onClick(()=> {
this.verifyCodeButtonText = `${this.countDown} s`
this.countDown--
this.verifyCodeButtonEnable = false
this.intervalId = setInterval(() => {
this.verifyCodeButtonText = `${this
if(this.countDown<0){
clearInterval(this.intervalId)
this.countDown = 10
this.intervalId = 0
this.verifyCodeButtonText = '获取验证码'
this.verifyCodeButtonEnable = true // 倒计时结束之后就可用了
return
}
this.countDown--
},1000)
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
}
.width('100%')
}
.height('100%')
}
}
封装成函数
@Entry
@Component
struct MyLoginCustom {
@State countDown: number = 10
intervalId: number = 0
@State verifyCodeButtonText: string = '获取验证码'
@State verifyCodeButtonEnable: boolean = true
waiting() {
this.verifyCodeButtonText = `${this.countDown} s`
this.countDown--
this.verifyCodeButtonEnable = false
this.intervalId = setInterval(() => {
this.verifyCodeButtonText = `${this
if(this.countDown<0){
clearInterval(this.intervalId)
this.countDown = 10
this.intervalId = 0
this.verifyCodeButtonText = '获取验证码'
this.verifyCodeButtonEnable = true // 倒计时结束之后就可用了
return
}
this.countDown--
},1000)
}
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
Button(this.verifyCodeButtonText)
.enabled(this.verifyCodeButtonEnable) // 当点击了,之后按钮不可用
.onClick(()=> {
this.waiting()
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
}
.width('100%')
}
.height('100%')
}
}
自行实现登录发送验证码
import cloud, { VerifyCodeAction } from '@hw-agconnect/cloud'
import hilog from '@ohos.hilog'
import router from '@ohos.router'
@Entry
@Component
struct MyLoginCustom {
@State countDown: number = 10
intervalId: number = 0
@State verifyCodeButtonText: string = '获取验证码'
@State verifyCodeButtonEnable: boolean = true
@State phoneNumber: string = ''
waiting() {
this.verifyCodeButtonText = `${this.countDown} s`
this.countDown--
this.verifyCodeButtonEnable = false
this.intervalId = setInterval(() => {
this.verifyCodeButtonText = `${this
if(this.countDown<0){
clearInterval(this.intervalId)
this.countDown = 10
this.intervalId = 0
this.verifyCodeButtonText = '获取验证码'
this.verifyCodeButtonEnable = true // 倒计时结束之后就可用了
return
}
this.countDown--
},1000)
}
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
.onChange(value => {
this.phoneNumber = value
})
Button(this.verifyCodeButtonText)
.enabled(this.verifyCodeButtonEnable) // 当点击了,之后按钮不可用
.onClick(async ()=> {
this.waiting()
try {
await cloud.auth().requestVerifyCode({
verifyCodeType: {
kind: 'phone',
phoneNumber: this.phoneNumber,
countryCode: '86'
},
action: VerifyCodeAction.REGISTER_LOGIN,
lang: 'zh_CN',
sendInterval: 10
})
hilog.info(0, 'VerifyCode', 'Success')
} catch (e) {
// 弹窗提示错误
AlertDialog.show({ title: '错误', message: '验证码发送失败' })
hilog.error(0, 'VerifyCode', JSON.stringify(e))
}
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
}
.width('100%')
}
.height('100%')
}
}
自行实现登录登录
import cloud, { VerifyCodeAction } from '@hw-agconnect/cloud'
import hilog from '@ohos.hilog'
import router from '@ohos.router'
@Entry
@Component
struct MyLoginCustom {
@State countDown: number = 10
intervalId: number = 0
@State verifyCodeButtonText: string = '获取验证码'
@State verifyCodeButtonEnable: boolean = true
@State phoneNumber: string = ''
@State verifyCode: string = ''
waiting() {
this.verifyCodeButtonText = `${this.countDown} s`
this.countDown--
this.verifyCodeButtonEnable = false
this.intervalId = setInterval(() => {
this.verifyCodeButtonText = `${this
if(this.countDown<0){
clearInterval(this.intervalId)
this.countDown = 10
this.intervalId = 0
this.verifyCodeButtonText = '获取验证码'
this.verifyCodeButtonEnable = true // 倒计时结束之后就可用了
return
}
this.countDown--
},1000)
}
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
.onChange(value => {
this.phoneNumber = value
})
Button(this.verifyCodeButtonText)
.enabled(this.verifyCodeButtonEnable) // 当点击了,之后按钮不可用
.onClick(async ()=> {
this.waiting()
try {
await cloud.auth().requestVerifyCode({
verifyCodeType: {
kind: 'phone',
phoneNumber: this.phoneNumber,
countryCode: '86'
},
action: VerifyCodeAction.REGISTER_LOGIN,
lang: 'zh_CN',
sendInterval: 10
})
hilog.info(0, 'VerifyCode', 'Success')
} catch (e) {
// 弹窗提示错误
AlertDialog.show({ title: '错误', message: '验证码发送失败' })
hilog.error(0, 'VerifyCode', JSON.stringify(e))
}
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
.onClick(async ()=> {
try {
const result = await cloud.auth().signIn({
credentialInfo: {
kind: 'phone',
countryCode: '86',
phoneNumber: this.phoneNumber,
verifyCode: this.verifyCode
}
})
const user = result.getUser()
hilog.info(0, 'Login', 'Success')
router.replaceUrl({ url: 'pages/MyWelcome' }) // 跳转到登录页
} catch (e) {
// 弹窗提示错误
AlertDialog.show({ title: '错误', message: '验证码发送失败' })
hilog.error(0, 'VerifyCode', JSON.stringify(e))
}
})
}
.width('100%')
}
.height('100%')
}
}
自行实现登录登录细节调整
检查用户输入的是否是手机号
import cloud, { VerifyCodeAction } from '@hw-agconnect/cloud'
import hilog from '@ohos.hilog'
import router from '@ohos.router'
@Entry
@Component
struct MyLoginCustom {
@State countDown: number = 10
intervalId: number = 0
@State verifyCodeButtonText: string = '获取验证码'
@State verifyCodeButtonEnable: boolean = true
@State phoneNumber: string = ''
@State verifyCode: string = ''
waiting() {
this.verifyCodeButtonText = `${this.countDown} s`
this.countDown--
this.verifyCodeButtonEnable = false
this.intervalId = setInterval(() => {
this.verifyCodeButtonText = `${this
if(this.countDown<0){
clearInterval(this.intervalId)
this.countDown = 10
this.intervalId = 0
this.verifyCodeButtonText = '获取验证码'
this.verifyCodeButtonEnable = true // 倒计时结束之后就可用了
return
}
this.countDown--
},1000)
}
async login() {
try {
const result = await cloud.auth().signIn({
credentialInfo: {
kind: 'phone',
countryCode: '86',
phoneNumber: this.phoneNumber,
verifyCode: this.verifyCode
}
})
const user = result.getUser()
AppStorage.SetOrCreate('user', user) // 存
hilog.info(0, 'Login', 'Success')
router.replaceUrl({ url: this.mainPage })
} catch (e) {
hilog.error(0, 'Login', JSON.stringify(e))
AlertDialog.show({ title: '错误', message: `登录失败 ${JSON.stringify(e)}` })
}
}
async sending() {
try {
await cloud.auth().requestVerifyCode({
verifyCodeType: {
kind: 'phone',
phoneNumber: this.phoneNumber,
countryCode: '86'
},
action: VerifyCodeAction.REGISTER_LOGIN,
lang: 'zh_CN',
sendInterval: 10
})
hilog.info(0, 'VerifyCode', 'Success')
} catch (e) {
AlertDialog.show({ title: '错误', message: '验证码发送失败' })
hilog.error(0, 'VerifyCode', JSON.stringify(e))
}
}
build() {
Row() {
Column({ space: 10 }) {
Text('登录')
.fontSize(40)
.fontWeight(FontWeight.Bold)
Divider()
TextInput({ placeholder: '请输入手机号' })
.width('80%')
.type(InputType.Number)
Row({ space: 10 }) {
TextInput({ placeholder: '请输入验证码' })
.width('55%')
.type(InputType.Number)
.onChange(value => {
this.phoneNumber = value
})
Button(this.verifyCodeButtonText)
.enabled(this.verifyCodeButtonEnable) // 当点击了,之后按钮不可用
.onClick(async ()=> {
this.waiting()
this.sending()
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceBetween)
Button('登录')
// 判断是否是合法的手机号,验证码
.enabled(this.phoneNumber.length === 11 && this.verifyCode.length === 6)
.onClick(async ()=> {
this.login()
})
}
.width('100%')
}
.height('100%')
}
}
个人设置页-登出
个人设置页修改昵称和头像
不用户信息存起来
取出头像信息
云存储
上传头像
开通云存储服务
图片缓存问题
工程模式
进入工程模式
用户授权
想使用云存储需要下面三步
1、开通云存储服务
2、更新项目的一个AGC的配置文件
3、用户授权,允许读取媒体文件(前面为什么没有,授权呢?因为我们使用的是端云一体化模板,配置都写好了)
下面我们就看看,模板帮我们写了那些配置
上传进度
问题4:上传进度实现
1.上传状态变量
2…上传进度文字变量、对应的Text
3.编写进度回调
import cloud, { AuthUser } from '@hw-agconnect/cloud'
import router from '@ohos.router'
import hilog from '@ohos.hilog'
import picker from '@ohos.file.picker'
@Entry
@Component
struct MyIndex {
@State photoUrl: string = ''
@State displayName: string = ''
@StorageLink('user') user: AuthUser = null
@State uploading: boolean = false
@State uploadingText: string = '0%'
aboutToAppear() {
// 1. cloud.auth().getCurrentUser()
// 2. AppStorage
this.displayName = this.user?.getDisplayName()
this.photoUrl = this.user?.getPhotoUrl()
}
build() {
Row() {
Column({ space: 10 }) {
Stack() { // 堆叠的效果
Image(this.photoUrl ? this.photoUrl : $r('app.media.user_dark'))
.width(70)
.height(70)
.borderRadius(70)
.enabled(!this.uploading)
.onComplete(()=>{
this.uploading = false
})
.onClick(async () => {
// this.photoUrl = 'https://img.zcool.cn/community/01a6095f110b9fa8012066219b67d4.png@1280w_1l_2o_100sh.png'
try {
// 1. 从相簿中选照片
const options = new picker.PhotoSelectOptions()
options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
options.maxSelectNumber = 1
const result = await new picker.PhotoViewPicker().select(options)
hilog.info(0, 'Upload', `Picker Success ${result.photoUris[0]}`)
this.uploading = true
// 2. 调云存储 api 上传照片
await cloud.storage().upload({
localPath: result.photoUris[0],
cloudPath: `test/${this.user.getUid()}.jpg`,
onUploadProgress: event => {
const percent = Math.floor(100 * event.loaded / event.total)
this.uploadingText = `${percent}%`
}
})
hilog.info(0, 'Upload', 'Upload Success')
// 3. 获取上传照片的网络地址
const url = await cloud.storage().getDownloadURL(`test/${this.user.getUid()}.jpg`)
this.photoUrl = `${url}&ts=${new Date().getTime()}`
// this.uploading = false
hilog.info(0, 'Upload', `url: ${url}`)
} catch (e) {
hilog.error(0, 'Upload', JSON.stringify(e))
}
})
if (this.uploading) {
// 显示上传进度
Text(this.uploadingText)
.width(70)
.height(70)
.borderRadius(70)
.fontColor('white')
.backgroundColor('black')
.opacity(0.6)
.fontSize(24)
.fontWeight(FontWeight.Bolder)
.textAlign(TextAlign.Center)
}
}
TextInput({ placeholder: '请设置昵称', text: this.displayName })
.width('50%')
.onChange(value => {
this.displayName = value
})
Button(`保存`)
.onClick(async () => {
try {
await this.user.updateProfile({
displayName: this.displayName,
photoUrl: this.photoUrl
})
hilog.info(0, 'updateProfile', 'Success')
} catch (e) {
hilog.error(0, 'updateProfile', JSON.stringify(e))
}
})
Button(`登出`)
.onClick(async () => {
try {
await cloud.auth().signOut()
hilog.info(0, 'SignOut', 'Success')
router.replaceUrl({ url: 'pages/MyLoginCustom' })
} catch (e) {
hilog.error(0, 'SignOut', JSON.stringify(e))
}
})
}
.width('100%')
}
.height('100%')
}
}
在进程中直接结束程序(没有点击退出),再次点击登录,会报错
原因,我们虽然结束了程序,由于我们没有正常退出程序,用户会话还在,再次登录的时候出现错误
云数据库
概念
存储区,对象类型,对象
数据类型
数据类型 | 取值范围 | 说明 | 排序方式 |
---|---|---|---|
String | 最大长度200 | 如果字符串长度超过200,建议使用Text类型。 | 采用 UTF-8 编码的字节顺序 |
Boolean | true/false | - | false < true |
Byte | ( − 2 7 ) ∼ ( 2 7 − 1 ) (-2^7)\sim(2^7-1) (−27)∼(27−1) | - | 数字顺序 |
Short | ( − 2 15 ) ∼ ( 2 15 − 1 ) (-2^{15})\sim(2^{15}-1) (−215)∼(215−1) | - | |
Integer | ( − 2 31 ) ∼ ( 2 31 − 1 ) (-2^{31})\sim(2^{31}-1) (−231)∼(231−1) | - | |
Long | ( − 2 63 ) ∼ ( 2 63 − 1 ) (-2^{63})\sim(2^{63}-1) (−263)∼(263−1) | 由于JavaScript不支持数据类型“Long”,Web SDK通过引入第三方开源组件实现支持数据类型“Long”的能力。“Long”类型的使用方法请参考https://github.com/dcodeIO/long.js。 | |
Float | -3.40E+38 ~ +3.40E+38 | - | |
Double | -1.79E+308 ~ +1.79E+308 | - | |
ByteArray | - | 一般用于文件类型的数据存储,如图片、文档和视频等。在端侧时,使用Android开发应用时,以byte[]表示为字节数组。 | 采用 UTF-8 编码的字节顺序 |
Text | - | - | |
Date | - | - | 时间顺序 |
IntAutoIncrement | 1 ∼ ( 2 31 − 1 ) 1\sim(2^{31}-1) 1∼(231−1) | Android、HarmonyOS(Java)和iOS不支持此数据类型。 | 数字顺序 |
LongAutoIncrement | 1 ∼ ( 2 63 − 1 ) 1\sim(2^{63}-1) 1∼(263−1) | Android、HarmonyOS(Java)和iOS不支持此数据类型。 | 数字顺序 |
- 假设有自增类型 id,执行 upsert (insert+update)
- id 值在数据库有,更新 {id:1, name:‘李四’ , age:20}
- id 值在数据库无,按自增添加 {id:5, name:‘王五’ , age:16}
- id 值不给,也按自增添加 {name:‘赵六’ , age:16}
student 表
id name age
1 李四 20
2 王五 16
3 赵六 16
权限管理
【角色】共有四种
- World 所有人
- Authenticated 已认证
- Creator 创建者
- Administrator 管理员
【权限】共有三种:Read 查询、Upsert 新增和修改、Delete 删除
例如
article 表
id title
用户 111
1 探索自我成长的道路:我的心路历程
2 如何在忙碌的生活中保持身心健康
3 分享我的旅行经历:探索世界的美
用户 222
4 金融投资的新趋势:数字货币的崛起
5 未来科技发展展望:人工智能对社会的影响
- 用户111 未登录,查询所有文章 1 2 3 4 5
- 用户333 未登录,查询所有文章 1 2 3 4 5
- 用户111 未登录,添加新文章 失败
- 用户111 已登录,添加新文章 成功
- 用户111 已登录,删除文章1 成功
- 用户111 已登录,删除文章4 失败
- 用户111 已登录,修改文章4 成功
从上面我们可知,华为的云数据库,权限配置比较粗糙,还是需要配合业务代码,进行
它的权限设置如下:
"permissions": [
{
"rights": ["Read"],
"role": "World"
},
{
"rights": ["Read","Upsert"],
"role": "Authenticated"
},
{
"rights": ["Read","Upsert","Delete"],
"role": "Creator"
},
{
"rights": ["Read","Upsert","Delete"],
"role": "Administrator"
}
]
则有:
- 未认证(未登录)的用户只能查询
- 这时最好不要给 Upsert 权限,否则由未认证的用户新增的数据,除了管理员都不能删除
- 已认证的用户可以查询、新增、修改
- 已认证的用户自己新增的数据,自己可以删除,别人的数据不能删除(但可以修改)
- 已认证用户查询时,还是能查到其它用户的数据的,因此需要自己维护 uid 来控制数据权限
准备云数据库
存储区
新建对象的类型,其实就是新建表
另一种方法准备云数据库
使用云端一体化模板
新建一张表
端侧调用云数据库
调用云数据库又分成了两种方式
一种叫端测调用:使用ArkTs它的API,然后直接去使用云数据库
另外一种叫云测调用: 云函数间接的使用云数据库
首先明确:云数据库需要对接华为帐号系统(与前面提到的权限相关联)
分成两种方式:
一. 直接在端侧调用数据库,优点是更直接面对数据库,而且根据文档具备缓存模式的好处
二. 通过云函数在云侧调用数据库,可能无法利用缓存模式的一些好处(未验证)。但又可以隐藏数据库细节,并具备云函数的优点
端侧调用
0)前提
- 添加依赖
- 下载 AGC 配置文件
- 初始化 AGC 连接器
这三步与调用云函数相同
long类型。js不支持,需要用到第三方库
每次我们做了新的配置,都要重新下载 AGC 配置文件
初始化 AGC 连接器
1)建立模型
js 代表端侧模型代码
serverSDK代表云侧模型代码
导出结果如下
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
* Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
*/
class t_user {
constructor() {
this.id = undefined;
this.name = "";
this.birthday = undefined;
}
setId(id) {
this.id = id;
}
getId() {
return this.id;
}
setName(name) {
this.name = name;
}
getName() {
return this.name;
}
setBirthday(birthday) {
this.birthday = birthday;
}
getBirthday() {
return this.birthday;
}
}
t_user.className = 't_user';
export {t_user}
2)导出 schema
导出数据库 schema 文件,例如下面是一个文件名 com.example.myapplication_21_cn.json,格式为【应用名_版本_语言.json】
3)初始化 database
改进–分页查询
改进–搜索功能
改进下滑显示搜索框
只有下滑到顶部的时候,才显示搜索框
// @ts-ignore
import schema from './com.example.myapplication_21_cn.json'
@Entry
@Component
struct XXX {
// ...
private database: Database
async aboutToAppear() {
try {
// 根据 schema 和 zoneName 创建数据库对象
this.database = cloud.database({ objectTypeInfo: schema, zoneName: "shopping" })
} catch (e) {
hilog.error(0, 'User Query', 'error:' + JSON.stringify(e))
}
}
4)查询
import cloud, { Database } from '@hw-agconnect/cloud'
// @ts-ignore
import schema from '../../resources/rawfile/schema.json'
import { t_student } from '../model/t_student'
import hilog from '@ohos.hilog'
@Entry
@Component
struct StudentPage {
@State studentList: t_student[] = []
private database: Database = null
private dataOffset: number = 0
@State searchAge: string = ''
@State showSearch: boolean = false
private startY: number = 0
private startIndex: number = 0
aboutToAppear() {
this.database = cloud.database({
zoneName: 'test',
objectTypeInfo: schema
})
this.search('', '', '', this.dataOffset)
}
async search(id: string, name: string, age: string, offset: number, limit: number = 10) {
try {
const query = this.database.collection(t_student).query()
if (id.length > 0) {
// where id=?
query.equalTo("id", Number(id))
}
if (name.length > 0) {
// where name like ?
query.contains("name", name)
}
if (age.length > 0) {
// where age=?
query.equalTo("age", Number(age))
}
query.limit(limit, offset)
query.orderByAsc("id")
const list: t_student[] = await query.get()
this.studentList.push(...list)
hilog.info(0, 'Query', `${list.map(s => s.id)}`)
} catch (e) {
hilog.error(0, 'Query', JSON.stringify(e))
}
}
build() {
Column({ space: 26 }) {
Row() {
Text(`编号`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('white')
.textAlign(TextAlign.Center)
.width('33%')
Text(`姓名`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('white')
.textAlign(TextAlign.Center)
.width('34%')
Text(`年龄`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('white')
.textAlign(TextAlign.Center)
.width('33%')
}
.width('100%')
.height(36)
.backgroundColor('black')
if (this.showSearch) {
Search()
.searchButton('搜索')
.onChange(value => {
this.searchAge = value
})
.onSubmit(() => {
this.studentList = []
this.dataOffset = 0
this.search('', '', this.searchAge, this.dataOffset)
this.showSearch = false
})
}
List({ space: 52 }) {
ForEach(this.studentList, (stu: t_student) => {
ListItem() {
Row() {
Text(`${stu.getId()}`)
.fontSize(18)
.textAlign(TextAlign.Center)
.width('33%')
Text(stu.getName())
.fontSize(18)
.textAlign(TextAlign.Center)
.width('34%')
Text(`${stu.getAge()}`)
.fontSize(18)
.textAlign(TextAlign.Center)
.width('33%')
}
.width('100%')
}
})
}
.width('100%')
.height('82%')
.onReachEnd(() => {
this.dataOffset += 10
this.search('', '', '', this.dataOffset)
})
.onScrollIndex((start, end) => {
hilog.info(0, 'Scroll', `start: ${start} end:${end}`)
this.startIndex = start
})
.onTouch(event => {
switch (event.type) {
case TouchType.Down:
this.startY = event.touches[0].y
break;
case TouchType.Move:
const endY = event.touches[0].y
if (endY - this.startY >= 35 && this.startIndex === 0) {
this.showSearch = true
} else if (this.startY - endY >= 25) {
this.showSearch = false
}
break;
}
})
Divider()
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
}
实现左滑删除
有个难点bind
函数作为参数传递,this丢失问题
函数作为参数传递,剩余参数问问题
问题演示
问题出现
5)新增、修改、删除
import cloud, { Database } from '@hw-agconnect/cloud'
// @ts-ignore
import schema from '../../resources/rawfile/schema.json'
import { t_student } from '../model/t_student'
import hilog from '@ohos.hilog'
import { StudentInsertDialog } from './StudentInsertDialog'
import router from '@ohos.router'
import { StudentUpdateDialog } from './StudentUpdateDialog'
@Entry
@Component
struct StudentPage {
@State studentList: t_student[] = []
private database: Database = null
private dataOffset: number = 0
@State searchAge: string = ''
@State showSearch: boolean = false
private startY: number = 0
private startIndex: number = 0
private studentInsertDialogController: CustomDialogController = new CustomDialogController({
builder: StudentInsertDialog({
confirm: async (name, age) => {
try {
const stu = new t_student()
stu.setName(name)
stu.setAge(age)
await this.database.collection(t_student).upsert(stu)
AlertDialog.show({ message: '添加学生成功' })
hilog.info(0, 'Student Insert', 'Success')
this.dataOffset = 0
this.studentList = []
this.search('', '', '', this.dataOffset)
} catch (e) {
hilog.error(0, 'Student Insert', JSON.stringify(e))
}
}
})
})
private studentUpdateDialogController: CustomDialogController
openStudentUpdateDialog(stu: t_student) {
this.studentUpdateDialogController = new CustomDialogController({
builder: StudentUpdateDialog({
sid: stu.id, name: stu.name, age: stu.age,
confirm: async (id, name, age) => {
try {
const stu = new t_student()
stu.setId(id)
stu.setName(name)
stu.setAge(age)
await this.database.collection(t_student).upsert(stu)
AlertDialog.show({ message: '修改学生成功' })
hilog.info(0, 'Student Update', 'Success')
this.dataOffset = 0
this.studentList = []
this.search('', '', '', this.dataOffset)
} catch (e) {
hilog.error(0, 'Student Update', JSON.stringify(e))
}
}
})
})
this.studentUpdateDialogController.open()
}
openStudentDeleteDialog(stu: t_student) {
AlertDialog.show({
title: '请确认',
message: '真的要删除该用户吗?',
confirm: {
value: '确定',
action: async () => {
try {
await this.database.collection(t_student).delete(stu)
AlertDialog.show({ message: '删除学生成功' })
hilog.info(0, 'Student Delete', 'Success')
this.dataOffset = 0
this.studentList = []
this.search('', '', '', this.dataOffset)
} catch (e) {
hilog.error(0, 'Student Delete', JSON.stringify(e))
}
}
}
})
}
@Builder
showOperation(stu: t_student) {
Row({ space: 12 }) {
Image($r("app.media.ic_public_edit_filled"))
.width(20)
.onClick(() => {
this.openStudentUpdateDialog(stu)
})
Image($r("app.media.ic_public_delete_filled"))
.width(21)
.onClick(() => {
this.openStudentDeleteDialog(stu)
})
}
.width(80)
.justifyContent(FlexAlign.Start)
}
aboutToAppear() {
this.database = cloud.database({
zoneName: 'test',
objectTypeInfo: schema
})
this.search('', '', '', this.dataOffset)
}
async search(id: string, name: string, age: string, offset: number, limit: number = 10) {
try {
const query = this.database.collection(t_student).query()
if (id.length > 0) {
// where id=?
query.equalTo("id", Number(id))
}
if (name.length > 0) {
// where name like ?
query.contains("name", name)
}
if (age.length > 0) {
// where age=?
query.equalTo("age", Number(age))
}
query.limit(limit, offset)
query.orderByAsc("id")
const list: t_student[] = await query.get()
this.studentList.push(...list)
hilog.info(0, 'Student Query', `${list.map(s => s.id)}`)
} catch (e) {
hilog.error(0, 'Student Query', JSON.stringify(e))
}
}
build() {
Column() {
Row() {
Text(`编号`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('white')
.textAlign(TextAlign.Center)
.width('33%')
Text(`姓名`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('white')
.textAlign(TextAlign.Center)
.width('34%')
Text(`年龄`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('white')
.textAlign(TextAlign.Center)
.width('33%')
}
.width('100%')
.height(36)
.backgroundColor('black')
if (this.showSearch) {
Search()
.searchButton('搜索')
.onChange(value => {
this.searchAge = value
})
.onSubmit(() => {
this.studentList = []
this.dataOffset = 0
this.search('', '', this.searchAge, this.dataOffset)
this.showSearch = false
})
}
List({ space: 48 }) {
ForEach(this.studentList, (stu: t_student) => {
ListItem() {
Row() {
Text(`${stu.getId()}`)
.fontSize(18)
.textAlign(TextAlign.Center)
.width('33%')
Text(stu.getName())
.fontSize(18)
.textAlign(TextAlign.Center)
.width('34%')
Text(`${stu.getAge()}`)
.fontSize(18)
.textAlign(TextAlign.Center)
.width('33%')
}
.width('100%')
}
.swipeAction({ end: this.showOperation.bind(this, stu) })
})
}
.width('100%')
.height('78%')
.margin({ top: 24, bottom: 24 })
.onReachEnd(() => {
this.dataOffset += 10
this.search('', '', '', this.dataOffset)
})
.onScrollIndex((start, end) => {
hilog.info(0, 'Scroll', `start: ${start} end:${end}`)
this.startIndex = start
})
.onTouch(event => {
switch (event.type) {
case TouchType.Down:
this.startY = event.touches[0].y
break;
case TouchType.Move:
const endY = event.touches[0].y
if (endY - this.startY >= 25 && this.startIndex === 0) {
this.showSearch = true
} else if (this.startY - endY >= 5 || this.startIndex === 0) {
this.showSearch = false
}
break;
}
})
Button('新增', { type: ButtonType.Normal })
.width('100%')
.margin({ bottom: 6 })
.onClick(async () => {
this.studentInsertDialogController.open()
})
Button('登出', { type: ButtonType.Normal })
.width('100%')
.backgroundColor(0xcccccc)
.margin({ bottom: 6 })
.onClick(async () => {
try {
await cloud.auth().signOut()
hilog.info(0, 'SignOut', 'Success')
router.replaceUrl({ url: 'pages/MyLoginCustom' })
} catch (e) {
hilog.error(0, 'SignOut', JSON.stringify(e))
}
})
Divider()
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
}
@CustomDialog
export struct StudentInsertDialog {
private name: string = ''
private age: number = 18
controller: CustomDialogController
confirm: (name: string, age: number) => void
private static range = (start, stop, step = 1) => Array.from({
length: (stop - start - 1) / step + 1
}, (_, i) => start + (i * step))
static AgeRange: string[] = StudentInsertDialog.range(16, 60).map(i => String(i))
build() {
Column({ space: 15 }) {
Row() {
Text('姓名')
.width('20%')
TextInput({ text: this.name })
.width('80%')
.onChange(value => {
this.name = value
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceAround)
Row() {
Text('年龄')
.width('20%')
TextPicker({ range: StudentInsertDialog.AgeRange, selected: 2 })
.width('80%')
.onChange(value => {
this.age = Number(value)
})
}
.width('80%')
Row() {
Button('取消')
.onClick(() => {
this.controller.close()
})
.backgroundColor(0xcccccc)
Button('确定')
.onClick(() => {
this.confirm(this.name, this.age)
this.controller.close()
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceAround)
}
.justifyContent(FlexAlign.SpaceAround)
.width('80%')
.height('55%')
.margin({ top: 15 })
}
}
import { StudentInsertDialog } from './StudentInsertDialog'
@CustomDialog
export struct StudentUpdateDialog {
private sid: number
private name: string
private age: number
controller: CustomDialogController
confirm: (id: number, name: string, age: number) => void
build() {
Column({ space: 15 }) {
Text(`${this.sid}`)
Row() {
Text('姓名')
.width('20%')
TextInput({ text: this.name })
.width('80%')
.onChange(value => {
this.name = value
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceAround)
Row() {
Text('年龄')
.width('20%')
TextPicker({ range: StudentInsertDialog.AgeRange, selected: 2 })
.width('80%')
.onChange(value => {
this.age = Number(value)
})
}
.width('80%')
Row() {
Button('取消')
.onClick(() => {
this.controller.close()
})
.backgroundColor(0xcccccc)
Button('确定')
.onClick(() => {
this.confirm(this.sid, this.name, this.age)
this.controller.close()
})
}
.width('80%')
.justifyContent(FlexAlign.SpaceAround)
}
.justifyContent(FlexAlign.SpaceAround)
.width('80%')
.height('65%')
.margin({ top: 15 })
}
}
云侧调用
这部分代码可以自动生成,不作为重点讲解