Open Harmony开发之分布式账本

简介

Demo基于Open Harmony系统使用ETS语言进行编写,本Demo主要通过设备认证、分布式拉起、分布式数据管理等功能来实现。

应用效果

设备认证,获取同一个局域网内的设备ID,并拉起应用
添加数据并在另一台设备显示该数据

开发步骤

1.新建Openharmony ETS工程

在DevEco Studio中点击File -> New Project ->[Standard]Empty Ability->Next,Language 选择ETS语言,最后点击Finish即创建成功。

2.界面代码编写
首页界面
 build() {
    Flex({ direction: FlexDirection.Column}) {
      //发现设备
      Button('发现设备', { type: ButtonType.Normal, stateEffect: true })
        .borderRadius(8)
        .backgroundColor(0x317aff).width(90)
        .onClick(() =>{
          this.fun()
        })
      //设备认证
      Button('authDevice', { type: ButtonType.Normal, stateEffect: true })
        .borderRadius(8)
        .backgroundColor(0x317aff).width(90)
        .onClick(() =>{
          this.authDevice()
        })

    // 拉起应用
      Button('拉起应用', { type: ButtonType.Normal, stateEffect: true })
        .borderRadius(8)
        .backgroundColor(0x317aff).width(90)
        .onClick(() =>{
                    this.startAb()
        })

      Stack({
        alignContent:Alignment.TopEnd
      }){
        Text('家庭账本')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .margin({left:12})
           .onClick(() =>{
             //          routePage()
             this.fun()
           })
        Image("/picture/add.png")
          .width(40)
          .height(40)
          .align(Alignment.Start)
          .margin({
            right:12
          }).onClick(() =>{
          routePage()
        })

      }
      .width(350)
      .height(60)
      .margin({
        top:10,
        bottom:10
      })
      Flex({
        direction: FlexDirection.Column, alignItems:ItemAlign.Start,
      }){

        Text("2022年12月")
          .fontSize(20)
          .fontColor(Color.White)

        Text("结余")
          .fontSize(20)
          .fontColor(Color.White)
          .margin({
            top:30
          }).align(Alignment.Start)

        Text("总支出0|总收入0")
          .fontSize(16)
          .fontColor(Color.White)
          .margin({
            top:10
          }).align(Alignment.Start)
      }
      .backgroundColor("#665A5A")
      .height(230)
      .layoutWeight(1)
      .padding(10)
      .onClick(() =>{
        routePage()
      })

      Tabs() {
        TabContent() {
          ProjectList()
        }
        .tabBar("项目")

        TabContent() {
          Statistics()
        }
        .tabBar("统计")
      }
    }
    .width('100%')
    .height('100%')
    .padding({
      left:12,right:12
    })

  }

底部TabContent 项目模块

@Component
struct ProjectList {
  remoteDataManager = new RemoteDataManager()
  @State ProjectData: Array<any> = []
  TestData:any[] =  []
  TestKvData: Array<any> = []
  kvManager = null
  kvStore = null
  STORE_ID = 'store_accountbook'
  aboutToAppear(){
    try {
      const config = {
        userInfo: {
          userId: '0',
          userType: 0
        },
        bundleName: 'com.example.accountbookets'
      }
      factory.createKVManager(config).then((manager) => {
        this.kvManager = manager
        let options =
          {
            createIfMissing: true,
            encrypt: false,
            backup: false,
            autoSync: true,
            kvStoreType: 1,
            securityLevel: 3
          }
        this.kvManager.getKVStore(this.STORE_ID, options).then((store) => {
          this.kvStore = store
          this.kvStore.get("key2").then((data) => {
            this.ProjectData = JSON.parse(data)
          })

        }).catch((err) => {
        })

      }).catch((err) => {
      })
    } catch (e) {
    }
  }

  @Builder ProjectItem(image, name, des,time,money) {
    Flex({ direction: FlexDirection.Row,alignItems: ItemAlign.Center }){
      Image($r("app.media.icon1"))
        .width(30)
        .height(30)

      Column() {
        Text(name)
          .fontSize(16)
          .fontColor(Color.Black)
        Text('11:20')
          .fontSize(16)
          .fontColor(Color.Gray)
      }
      .alignItems(HorizontalAlign.Start)
      .margin({left:15})

      Text('HUAWEI')
        .fontSize(12)
        .fontColor(Color.Black)
        .margin({left:20})

      Text(des)
        .fontSize(14)
        .fontColor(Color.Gray)
        .margin({left:15})

      Column() {
        Text('-100')
          .fontSize(16)
          .fontColor(Color.Black)
        Text(time)
          .fontSize(16)
          .fontColor(Color.Gray)
      }
      .alignItems(HorizontalAlign.Start)
      .margin({left:20})
    }
    .width(400)
    .height(50)
    .margin({top:10})

  }

  build() {
    List() {
      ForEach(this.ProjectData, (item) => {
        ListItem() {
          this.ProjectItem(item.image, item.name, item.des,item.time,item.money)
        }
        .onClick(() => {
        })

      }, (item) => JSON.stringify(item)) {

      }


    }
  }
}

底部TabContent 统计模块

@Component
struct Statistics{
  build(){
    Flex({ direction: FlexDirection.Row}){

      Tabs() {
        TabContent() {
          PayList()
        }
        .tabBar("支出分类")

        TabContent() {

        }
        .tabBar("成员分类")
      }
    }
  }
}

统计模块里面的TabContent

@Component
struct PayList {

  private PayData: PayBean[] = initializeOnStartup()
  @Builder PayItem(previewUrl, title, describe) {
    Flex({ direction: FlexDirection.Row,alignItems: ItemAlign.Center }){
      Image(previewUrl)
        .width(30)
        .height(30)


      Text(title)
        .fontSize(16)
        .fontColor(Color.Black)
        .margin({left:8})

      Text('100%')
        .fontSize(12)
        .fontColor(Color.Black)
        .margin({left:8})

      Progress({ value: 20, total: 150, style: ProgressStyle.Linear }).color(Color.Red).value(150).width(200)

      Text('-100')
        .fontSize(14)
        .fontColor(Color.Gray)
        .margin({left:8})
    }
    .width(400)
    .height(50)
    .margin({top:10})

  }

  build() {
    List() {
      ForEach(this.PayData, (item) => {
        ListItem() {
          this.PayItem(item.image, item.name, item.des)
        }
        .onClick(() => {
          console.info("点击我")
          router.push({
            uri: "pages/VideoPlayer",

          })
        })
      }, (item) => JSON.stringify(item)) {

      }
    }
  }
}
2) add.ets页面
build() {
    Flex({ direction: FlexDirection.Column,alignItems: ItemAlign.Center}) {

      Flex({ direction: FlexDirection.Row,alignItems: ItemAlign.Center})
      {
        Image("/picture/icon_back.png")
          .width(35)
          .height(35)
         .onClick(() =>{
           router.push({
             uri: "pages/index",

           })
         })

        Text("加一笔")
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        .margin({left:20})
      }.margin({top:10})
      .padding({left:20})
      .height(100)
      .width(500)
      Stack({
        alignContent: Alignment.TopStart
      }){
        Tabs() {
          TabContent() {
            pay({payTime:$strTime,payRemark:$strRemark,payType:$strType})
          }
          .tabBar("支出")

          TabContent() {
            Income()
          }
          .tabBar("收入")
        }
        .height(450)
      }.width(500)
      .height(500)
      Flex({
        direction: FlexDirection.Row,alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center
      }){
        Text("输入金额")
          .fontColor(Color.Black)
          .fontSize(20)
          .margin({
            right:15
          })
          .width(100)
        TextInput({ placeholder: '100', text:this.strMoney })
          .type(InputType.Normal)
          .placeholderColor(Color.Gray)
          .placeholderFont({ size: 20, weight: 2})
          .enterKeyType(EnterKeyType.Search)
          .caretColor(Color.Green)
          .width(250)
          .height(40)
          .borderRadius('20px')
      }
      .width(400)
      .height(50)

      Text('保存')
        .fontColor(Color.White)
        .fontSize(20)
        .margin({
          top:20
        })
        .textAlign(TextAlign.Center)
      .width(380)
        .height(80)
      .backgroundColor("#FE4F16")
        .onClick(() =>{

          TestData.push({image:"/picture/icon1.png",title:'canyin',des:'ceshi',time:'2021',money:'50'})
          if (AppStorage.Get("key1") == null) {
            AppStorage.SetAndLink("key1", TestData)
            this.remoteDataManager.dataChange("key2", JSON.stringify(TestData))
          }else{
            this.TestStorageData = AppStorage.Get("key1")
            //
//            this.TestStorageData.push({image:"/picture/icon1.png",title:'canyin',des:'beizhu',time:'2021',money:'50'})
            //具体代码
            this.TestStorageData.push({image:"/picture/icon1.png",title:this.strType,des:this.strRemark,time:this.strTime,money:this.strMoney})
            AppStorage.SetAndLink("key1", this.TestStorageData)
            let str = JSON.stringify(this.TestStorageData)
            this.TestKvData = JSON.parse(str)
            this.remoteDataManager.dataChange("key2", JSON.stringify(this.TestKvData))
          }
          router.push({
            uri: "pages/index",

          })
        })

    }
    .width('100%')
    .height('100%')
  }

add页面支出模块

@Component
struct pay{
  @Link payTime:string
  @Link payRemark:string
  @Link payType:string
  @State private index:number = 0
  @State strType:string = "canyin"
  @State AccountData: Array<any> = [
    { previewUrl: "/picture/icon1.png", title: "canyin" ,number:0},
    { previewUrl: "/picture/icon2_2.png", title: "gouwu" ,number:1},
    { previewUrl: "/picture/icon3_3.png", title: "jiaotong" ,number:2},
    { previewUrl: "/picture/icon4_4.png", title: "fuwu" ,number:3},
    { previewUrl: "/picture/icon5_5.png", title: "jiaoyu" ,number:4},
    { previewUrl: "/picture/icon6_6.png", title: "yundong" ,number:5},
    { previewUrl: "/picture/icon7_7.png", title: "luxing" ,number:6},
    { previewUrl: "/picture/icon8_8.png", title: "yiliao" ,number:7},
//    { previewUrl: "/picture/icon9_9.png", title: "生活" ,number:9},
//    { previewUrl: "/picture/icon10_10.png", title: "宠物" ,number:10},
  ]

  @Builder ProItem(previewUrl, title,number) {
    Stack() {
      Flex({
        direction: FlexDirection.Column
      }) {
        if (this.index == number) {
          if (number == 0) {
            Image("/picture/icon1.png")
              .width(60)
              .height(60)
          }else if (number == 1) {
            Image("/picture/icon2.png")
              .width(60)
              .height(60)

          }else if (number == 2) {
            Image("/picture/icon3.png")
              .width(60)
              .height(60)

          }else if (number == 3) {
            Image("/picture/icon4.png")
              .width(60)
              .height(60)
          }else if (number == 4) {
            Image("/picture/icon5.png")
              .width(60)
              .height(60)

          }else if (number == 5) {
            Image("/picture/icon6.png")
              .width(60)
              .height(60)

          }else if (number == 6) {
            Image("/picture/icon7.png")
              .width(60)
              .height(60)
          }else if (number == 7) {
            Image("/picture/icon8.png")
              .width(60)
              .height(60)
          }else if (number == 8) {
            Image("/picture/icon9.png")
              .width(60)
              .height(60)
          }else if (number == 9) {
            Image("/picture/icon10.png")
              .width(60)
              .height(60)
          }
        }else{
          if (number == 0) {
            Image("/picture/icon1_1.png")
              .width(60)
              .height(60)
          }else{
            Image(previewUrl)
              .width(60)
              .height(60)
          }

        }
        Column() {
          Text(title)
            .fontSize(16)
            .fontColor(Color.Black)
        }
        .alignItems(HorizontalAlign.Center)
      }

    }
    .height(100)
    .width(100)
    .margin({
      bottom: 16
    })

  }
  build(){
    Flex({direction: FlexDirection.Column}){

      Grid(){
        ForEach(this.AccountData, (item) => {
          GridItem() {
            this.ProItem(item.previewUrl, item.title,item.number)
          }
          .onClick(() => {
            console.info("点击我")
            this.index = item.number
            this.payType = this.AccountData[this.index].title
          })
        }, (item) => JSON.stringify(item)) {

        }
      }
      .rowsTemplate('1fr 1fr')
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .columnsGap(8)
      .rowsGap(8)
      .height(200)
//      Time()
//      Remark()
      // ******************时间**********************
      Flex({
        direction: FlexDirection.Row,alignItems: ItemAlign.Center
      }){
        Text("时间")
          .fontColor(Color.Black)
          .fontSize(20)
          .margin({
            right:15
          })
          .width(70)

        TextInput({ placeholder: '输入收支时间', text: this.payTime })
          .type(InputType.Normal)
          .placeholderColor(Color.Gray)
          .placeholderFont({ size: 20, weight: 2})
          .enterKeyType(EnterKeyType.Search)
          .caretColor(Color.Green)
          .width(300)
          .height(40)
          .borderRadius('20px')
          .backgroundColor(Color.White)
          .onChange((value: string) => {
            this.payTime = value
          })

      }
      .margin({
        top:20,left:15
      })
      .width(200)
      //*******************备注********************
      Flex({
        direction: FlexDirection.Row,alignItems: ItemAlign.Center
      }){
        Text("备注")
          .fontColor(Color.Black)
          .fontSize(20)
          .margin({
            right:15
          })
          .width(70)

        TextInput({ placeholder: '输入说明', text: this.payRemark })
          .type(InputType.Normal)
          .placeholderColor(Color.Gray)
          .placeholderFont({ size: 20, weight: 2})
          .enterKeyType(EnterKeyType.Search)
          .caretColor(Color.Green)
        //        .layoutWeight(8)
          .height(40)
          .width(300)
          .borderRadius('20px')
          .backgroundColor(Color.White)
          .onChange((value: string) => {
            this.payRemark = value
          })
      }
      .margin({
        top:20,left:15
      })
      .width(50)
      .height(50)
    }
    .height('100%')
    .layoutWeight(1)

  }
}

收入模块代码

@Component
struct Income{

  build(){
    Flex({direction: FlexDirection.Column}){
      Time()
      Remark()

    }
  }
}

时间模块

@Component
struct Time{
  @State inputTime:string = ''
  build(){
    Flex({
      direction: FlexDirection.Row,alignItems: ItemAlign.Center
    }){
      Text("时间")
        .fontColor(Color.Black)
        .fontSize(20)
        .margin({
          right:15
        })
      .width(70)

      TextInput({ placeholder: '2021', text: this.inputTime })
        .type(InputType.Normal)
        .placeholderColor(Color.Gray)
        .placeholderFont({ size: 20, weight: 2})
        .enterKeyType(EnterKeyType.Search)
        .caretColor(Color.Green)
        .width(300)
        .height(40)
        .borderRadius('20px')
        .backgroundColor(Color.White)
    }
    .margin({
      top:20,left:15
    })
    .width(200)
  }
}

备注模块

@Component
struct Remark{
  @State inputRemark:string = ''
  build(){
    Flex({
      direction: FlexDirection.Row,alignItems: ItemAlign.Center
    }){
      Text("备注")
        .fontColor(Color.Black)
        .fontSize(20)
        .margin({
          right:15
        })
      .width(70)

      TextInput({ placeholder: 'ceshe', text: this.inputRemark })
        .type(InputType.Normal)
        .placeholderColor(Color.Gray)
        .placeholderFont({ size: 20, weight: 2})
        .enterKeyType(EnterKeyType.Search)
        .caretColor(Color.Green)
//        .layoutWeight(8)
        .height(40)
        .width(300)
        .borderRadius('20px')
        .backgroundColor(Color.White)

    }
    .margin({
      top:20,left:15
    })
    .width(50)
    .height(50)

  }
}
3.设备认证

设备认证是依赖 DeviceManager 组件来实现的,详细代码参考源码RemoteDeviceModel.ets

1.创建DeviceManager实例

 registerDeviceListCallback(callback) {
    if (typeof (this.#deviceManager) === 'undefined') {
      deviceManager.createDeviceManager('com.example.tictactoegame', (error, value) => {
        if (error) return
        this.#deviceManager = value;
        this.registerDeviceListCallback_(callback);
      });
    } else {
      this.registerDeviceListCallback_(callback);
    }
  }

2.查询可信设备列表

 var list = this.#deviceManager.getTrustedDeviceListSync();
    if (typeof (list) != 'undefined' && typeof (list.length) != 'undefined') {
      this.deviceList = list;
    }

3.注册设备上下线监听

this.#deviceManager.on('deviceStateChange', (data) => {
      switch (data.action) {
        case 0:
          this.deviceList[this.deviceList.length] = data.device;
          this.callback();
          if (this.authCallback != null) {
            this.authCallback();
            this.authCallback = null;
          }
          break;
        case 2:
          if (this.deviceList.length > 0) {
            for (var i = 0; i < this.deviceList.length; i++) {
              if (this.deviceList[i].deviceId === data.device.deviceId) {
                this.deviceList[i] = data.device;
                break;
              }
            }
          }
          this.callback();
          break;
        case 1:
          if (this.deviceList.length > 0) {
            var list = [];
            for (var i = 0; i < this.deviceList.length; i++) {
              if (this.deviceList[i].deviceId != data.device.deviceId) {
                list[i] = data.device;
              }
            }
            this.deviceList = list;
          }
          this.callback();
          break;
        default:
          break;
      }
    });

4.设备发现

this.#deviceManager.on('deviceFound', (data) => {
      for (let i = 0; i < this.discoverList.length; i++) {
        if (that.discoverList[i].deviceId === data.device.deviceId) {
          return;
        }
      }
      this.discoverList[this.discoverList.length] = data.device;
      this.callback();
    });

5.设备认证

authDevice(deviceInfo, callback){
    let extraInfo = {
      "targetPkgName": 'com.example.tictactoegame',
      "appName": 'com.example.tictactoegame',
      "appDescription": 'com.example.tictactoegame',
      "business": '0'
    };
    let authParam = {
      "authType": 1,
      "appIcon": '',
      "appThumbnail": '',
      "extraInfo": extraInfo
    };
    this.#deviceManager.authenticateDevice(deviceInfo, authParam, (err, data) => {
      if (err) {
        this.authCallback = null;
      } else {
        this.authCallback = callback;
      }
    });
  }
4.数据管理

分布式数据管理 依赖@ohos.data.distributedData模块实现,详细参考源码RemoteDataManager.ets

1.导入该模块

import factory from '@ohos.data.distributedData';

2.创建KVManager实例,用于管理数据库对象

  registerDataListCallback(callback) {
    let that = this
    if (this.kvManager == null) {
      try {
        const config = {
          userInfo: {
            userId: '0',
            userType: 0
          },
          bundleName: 'com.example.tictactoegame'
        }
        factory.createKVManager(config).then((manager) => {
          that.kvManager = manager
          that.registerDataListCallback_(callback)
        }).catch((err) => {
        })
      } catch (e) {
      }
    } else {
      this.registerDataListCallback_(callback)
    }
  }

3.创建并获取KVStore数据库

registerDataListCallback_(callback) {
    let that = this
    if (that.kvManager == null) {
      callback()
      return
    }
    if (that.kvStore == null) {
      try {
        let options =
          {
            createIfMissing: true,
            encrypt: false,
            backup: false,
            autoSync: true,
            kvStoreType: 1,
            securityLevel: 3
          }
        this.kvManager.getKVStore(this.STORE_ID, options).then((store) => {
          that.kvStore = store
          that._registerDataListCallback_(callback)
        }).catch((err) => {
        })
      } catch (e) {
      }
    } else {
      this._registerDataListCallback_(callback)
    }
  }

4.订阅指定类型的数据变更通知

  _registerDataListCallback_(callback) {
    let that = this
    if (that.kvManager == null) {
      callback()
      return
    }
    this.kvStore.on('dataChange', 1, function(data) {
      if (data) {
         that.arr = data.updateEntries
        callback()
      }
    })
  }

5.添加指定类型键值对到数据库

  startAbilityContinuation(deviceId) {
    let wantValue = {
      bundleName: 'com.example.tictactoegame',
      abilityName: 'com.example.tictactoegame.MainAbility',
      deviceId: deviceId,
      parameters: {
        uri: 'pages/Fight'
      }
    };
    featureAbility.startAbility({ want: wantValue }).then(() => {
      router.replace({ uri: 'pages/Fight' })
    });
  }
5.远程拉起设备app

使用 FeatureAbility 模块的startAbility接口拉起远程设备app

  startAbilityContinuation(deviceId) {
    let wantValue = {
      bundleName: 'com.example.tictactoegame',
      abilityName: 'com.example.tictactoegame.MainAbility',
      deviceId: deviceId,
      parameters: {
        uri: 'pages/Fight'
      }
    };
    featureAbility.startAbility({ want: wantValue }).then(() => {
      router.replace({ uri: 'pages/Fight' })
    });
  }
6.添加数据

新建一个账单数据 添加到分布式数据

this.remoteDataManager.dataChange("key2", JSON.stringify(this.TestKvData))

在另一台设备监听并获取显示该条数据

 private onPageShow() {

    this.remoteDataManager.registerDataListCallback(() => {
      let arr = this.remoteDataManager.arr[0]
      this.strTest = arr.value.value
      this.ProjectData = JSON.parse(this.strTest)
     
  }

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

入门必看:https://qr21.cn/FV7h05

  1. 应用开发导读(ArkTS)

  2. ......

HarmonyOS 概念:https://qr21.cn/FV7h05

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念

  2. 构建第一个ArkTS应用

  3. ......

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识

  2. 配置文件

  3. 应用数据管理

  4. 应用安全管理

  5. 应用隐私保护

  6. 三方应用调用管控机制

  7. 资源分类与访问

  8. 学习ArkTS语言

  9. ......

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发

  2. UI开发

  3. 公共事件与通知

  4. 窗口管理

  5. 媒体

  6. 安全

  7. 网络与链接

  8. 电话服务

  9. 数据管理

  10. 后台任务(Background Task)管理

  11. 设备管理

  12. 设备使用信息统计

  13. DFX

  14. 国际化开发

  15. 折叠屏系列

  16. ......

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

相关推荐
试行26 分钟前
Android实现自定义下拉列表绑定数据
android·java
Dingdangr5 小时前
Android中的Intent的作用
android
技术无疆5 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP5 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
逢生博客11 小时前
Mac 搭建仓颉语言开发环境(Cangjie SDK)
macos·华为·鸿蒙
青柠_项目管理11 小时前
PMP证书持有者,在华为、腾讯一般能拿多少薪资?
华为·pmp
Jouzzy12 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯13 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
小强在此14 小时前
【基于开源鸿蒙(OpenHarmony)的智慧农业综合应用系统】
华为·开源·团队开发·智慧农业·harmonyos·开源鸿蒙