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

相关推荐
Greenland_126 小时前
Android Gralde补全计划 productFlavors多渠道打包(变体/多客户)
android
Just_Paranoid6 小时前
【TaskStackListener】Android 中用于监听和响应任务栈
android·ams·task·taskstack
权泽谦6 小时前
从零搭建一个 PHP 登录注册系统(含完整源码)
android·开发语言·php
aaajj7 小时前
android contentprovider及其查看
android
fundroid13 小时前
Android Studio + Gemini:重塑安卓 AI 开发新范式
android·android studio·ai编程
vortex514 小时前
谷歌黑客语法挖掘 SQL 注入漏洞
android·数据库·sql
大师兄666814 小时前
鸿蒙 ArkTS 入门教程:小白实战 List 列表开发(详解 @State, ForEach, @Builder)
list·harmonyos·arkts·builder·foreach·state·鸿蒙入门
特立独行的猫a16 小时前
仓颉编程语言的并发编程:线程模型与使用实践
华为·线程·仓颉·仓颉语言
-指短琴长-17 小时前
MySQL快速入门——基本查询(下)
android·mysql·adb
stevenzqzq18 小时前
android lambda回调
android