HarmonyOS开发之设备唯一ID方案

在HarmonyOS应用开发中,设备唯一标识符(Device Unique Identifier)是确保用户账户安全和个性化体验的重要组成部分。HarmonyOS NEXT 提供了多种方式来获取设备标识符,包括OAID(Open Anonymous ID)和AAID(Android Advertising ID)。然而,这些标识符在某些情况下可能不够稳定,例如当用户重置设备或卸载应用后,这些标识符可能会发生变化。为了提供更加稳定的设备标识符,HarmonyOS NEXT 引入了 Asset Store Kit(关键资源存储开发服务),它允许开发者在设备上安全地存储和管理关键资源,如账号信息、密码等。

本文将详细介绍如何使用 Asset Store Kit 实现设备唯一ID的持久化存储,确保用户在卸载并重装应用后仍然能够保持原有的账户信息。

场景描述

假设我们有一个应用,用户在首次登录时需要输入账号和密码。一旦登录成功,即使用户卸载并重装应用,再次打开应用时也能够自动恢复之前的登录状态,无需重新输入账号和密码。

方案描述

1. 配置权限

首先,我们需要在 module.json5 文件中配置 ohos.permission.STORE_PERSISTENT_DATA 权限,以便使用 Asset Store Kit。

HarmonyOS开发之设备唯一ID方案_唯一标识

{
  "module": {
    "abilities": [
      {
        "name": ".MainAbility",
        "label": "$string:app_name",
        "icon": "$media:icon",
        "description": "$string:app_desc",
        "type": "page",
        "launchType": "standard",
        "orientation": "unspecified",
        "skills": [
          {
            "actions": ["action.system.home"]
          }
        ]
      }
    ],
    "reqPermissions": [
      "ohos.permission.STORE_PERSISTENT_DATA"
    ]
  }
}

2. 实现持久化存储

2.1 查询存储的数据

在应用启动时,我们需要检查是否存在已经存储的账号信息。如果存在,则直接使用这些信息;如果不存在,则提示用户进行登录。

async aboutToAppear(): Promise<void> {
  let query: asset.AssetMap = new Map();
  query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ATTRIBUTES); // 返回关键资源属性,不含关键资源明文。

  try {
    let res = asset.querySync(query);
    if (res.length > 0) {
      let alias = res[0].get(asset.Tag.ALIAS) as Uint8Array;
      let aliasStr = arrayToString(alias);

      let query2: asset.AssetMap = new Map();
      query2.set(asset.Tag.ALIAS, stringToArray(aliasStr));
      query2.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL); // 返回关键资源明文及属性

      let res2 = asset.querySync(query2);
      for (let i = 0; i < res2.length; i++) {
        let ID = res2[i].get(asset.Tag.SECRET) as Uint8Array;
        let IDStr = arrayToString(ID);
        let deviceType = res2[i].get(asset.Tag.DATA_LABEL_NORMAL_1) as Uint8Array;
        let deviceTypeStr = arrayToString(deviceType);

        // 存储查询到的账号信息
        let myList: MesList = new MesList(aliasStr, IDStr, deviceTypeStr);
        router.pushUrl({
          url: 'pages/Search_AssetLogin',
          params: { myList: myList }
        });
      }
    } else {
      console.log('暂无设备');
    }
  } catch (err) {
    console.error('查询失败', err);
  }
}
2.2 用户登录

当用户首次登录时,我们需要将账号信息存储到 Asset Store Kit 中。如果用户已经登录过,则直接使用存储的信息。

async function login(account: string, password: string) {
  let deviceTypeStr = deviceInfo.marketName;

  let query: asset.AssetMap = new Map();
  query.set(asset.Tag.ALIAS, stringToArray(account)); // 指定了关键资源别名,最多查询到一条满足条件的关键资源
  query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL); // 此处表示需要返回关键资源的所有信息,即属性+明文

  await asset.query(query).then((res: Array<asset.AssetMap>) => {
    if (res.length > 0) {
      for (let i = 0; i < res.length; i++) {
        let inputAccount = res[i].get(asset.Tag.ALIAS) as Uint8Array;
        let inputAccountStr = arrayToString(inputAccount);
        let ID = res[i].get(asset.Tag.SECRET) as Uint8Array;
        let IDStr = arrayToString(ID);
        let deviceType = res[i].get(asset.Tag.DATA_LABEL_NORMAL_1) as Uint8Array;
        let deviceTypeStr = arrayToString(deviceType);

        if (account === inputAccountStr && password === '123456') {
          let myList: MesList = new MesList(inputAccountStr, IDStr, deviceTypeStr);
          router.pushUrl({
            url: 'pages/Search_AssetLogin',
            params: { myList: myList }
          });
        } else {
          console.log('密码错误');
        }
      }
    } else {
      // 新设备,弹窗提示用户信任
      AlertDialog.show({
        title: '是否信任此设备',
        subtitle: '',
        message: '',
        autoCancel: true,
        alignment: DialogAlignment.Bottom,
        gridCount: 4,
        offset: { dx: 0, dy: -20 },
        primaryButton: {
          value: '取消',
          action: () => {
            console.log('请重新登录');
          }
        },
        secondaryButton: {
          enabled: true,
          defaultFocus: true,
          style: DialogButtonStyle.HIGHLIGHT,
          value: '确认',
          action: () => {
            let attr: asset.AssetMap = new Map();
            attr.set(asset.Tag.ALIAS, stringToArray(account));
            attr.set(asset.Tag.SECRET, stringToArray(state.ID));
            attr.set(asset.Tag.DATA_LABEL_NORMAL_1, stringToArray(deviceTypeStr));
            attr.set(asset.Tag.IS_PERSISTENT, true);

            try {
              asset.addSync(attr); // 第一次登录,弹窗点击信任添加数据
              console.log("登录成功");

              let query: asset.AssetMap = new Map();
              query.set(asset.Tag.ALIAS, stringToArray(account)); // 指定了关键资源别名,最多查询到一条满足条件的关键资源
              query.set(asset.Tag.RETURN_TYPE, asset.ReturnType.ALL); // 此处表示需要返回关键资源的所有信息,即属性+明文

              let res = asset.querySync(query);
              for (let i = 0; i < res.length; i++) {
                let inputAccount = res[i].get(asset.Tag.ALIAS) as Uint8Array;
                let inputAccountStr = arrayToString(inputAccount);
                let ID = res[i].get(asset.Tag.SECRET) as Uint8Array;
                let IDStr = arrayToString(ID);
                let deviceType = res[i].get(asset.Tag.DATA_LABEL_NORMAL_1) as Uint8Array;
                let deviceTypeStr = arrayToString(deviceType);

                let myList: MesList = new MesList(inputAccountStr, IDStr, deviceTypeStr);
                router.pushUrl({
                  url: 'pages/Search_AssetLogin',
                  params: { myList: myList }
                });
              }
            } catch (error) {
              if (error.code === 24000003) {
                console.log('请勿重复登录');
              } else {
                console.log('登录失败');
              }
            }
          }
        }
      });
    }
  }).catch((err: BusinessError) => {
    console.error('查询失败', err);
  });
}
2.3 删除存储的资源

在某些情况下,用户可能希望注销账户或删除存储的资源。我们可以通过 remove 方法来实现这一点。

export class One {
  async remove() {
    let query: asset.AssetMap = new Map();
    try {
      asset.remove(query).then(() => {
        console.info(`Asset removed successfully.`);
        router.pushUrl({
          url: 'pages/Asset_login'
        });
        console.log('请重新登录');
      }).catch((err: BusinessError) => {
        console.error(`Failed to remove Asset. Code is ${err.code}, message is ${err.message}`);
      });
    } catch (error) {
      let err = error as BusinessError;
      console.error(`Failed to remove Asset. Code is ${err.code}, message is ${err.message}`);
    }
  }
}

辅助函数

为了方便处理字符串和 Uint8Array 之间的转换,我们可以定义一些辅助函数。

function arrayToString(array: Uint8Array): string {
  return new TextDecoder().decode(array);
}

function stringToArray(str: string): Uint8Array {
  return new TextEncoder().encode(str);
}


上一篇:【无标题】


下一篇:2024.9.26C++作业