openharmony之分布式购物车开发实战

目标与准备

目标: 构建一个分布式购物车应用,实现商品列表在多台设备间的实时、双向同步。
核心流程:

  1. 设备组网: 用户在应用内发现并信任其他设备,建立分布式网络。
  2. 数据操作: 用户在任一设备上添加或删除购物车商品。
  3. 自动同步: 数据变更通过分布式 KV Store 自动同步到所有已入网设备。
  4. UI 刷新: 所有设备上的 UI 自动响应数据变化,实时更新显示。
    开发环境与准备:
  5. 硬件: 至少两台搭载 OpenHarmony 标准系统的设备(如手机、平板)。
  6. 软件:
    • OpenHarmony SDK API 12+ (Full SDK)。
    • 重要: 分布式相关 API 属于系统级接口,必须 在 DevEco Studio 中配置并使用 Full SDK,否则编译会因找不到符号而失败。

第一部分:项目准备与权限配置

步骤 1:创建新项目

在 DevEco Studio 中,选择 "Create Project",创建一个 "Empty Ability" 模板的工程,Language 选择 ArkTS,Compatibility SDK 选择 12以上

步骤 2:配置 module.json5 权限

分布式功能需要申请敏感权限。打开 entry/src/main/module.json5 文件,在 requestPermissions 数组中添加以下权限:

json 复制代码
{
  "module": {
    // ...其他配置
    "requestPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC",
        "reason": "$string:permission_reason_distributed_datasync",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.ACCESS_SERVICE_DM",
        "reason": "$string:permission_reason_access_service_dm",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
}
  • DISTRIBUTED_DATASYNC: 用于分布式数据同步。
  • ACCESS_SERVICE_DM: 用于设备发现和管理。
    同时,在 resources/base/element/string.json 中添加权限说明文本:
json 复制代码
{
  "string": [
    // ...其他字符串
    {
      "name": "permission_reason_distributed_datasync",
      "value": "用于在不同设备间同步您的购物车数据,提供无缝体验"
    },
    {
      "name": "permission_reason_access_service_dm",
      "value": "用于发现和连接附近的信任设备,以实现分布式功能"
    }
  ]
}
步骤 3:动态请求权限

在应用启动时,需要动态请求这些权限。在 EntryAbility.etsonCreate 方法中添加权限请求逻辑。

typescript 复制代码
// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import { abilityAccessCtrl, bundleManager, Permissions } from '@ohos.abilityAccessCtrl';
import { BusinessError } from '@ohos.base';
export default class EntryAbility extends UIAbility {
  async onRequestPermissions() {
    const atManager = abilityAccessCtrl.createAtManager();
    const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    const tokenId = bundleInfo.appInfo.accessTokenId;
    const permissions: Array<Permissions> = [
      'ohos.permission.DISTRIBUTED_DATASYNC',
      'ohos.permission.ACCESS_SERVICE_DM'
    ];
    const result = await atManager.requestPermissionsFromUser(this.context, permissions);
    hilog.info(0x0000, 'PermissionTag', `Permission request result: ${JSON.stringify(result)}`);
  }
  onCreate(want, launchParam) {
    hilog.info(0x0000, 'AbilityTag', '%{public}s', 'Ability onCreate');
    this.onRequestPermissions();
  }
  // ...其他生命周期方法
}

第二部分:核心服务封装

为了实现关注点分离,我们将所有分布式业务的逻辑封装到一个独立的服务类 DistributedCartService 中。这使得 UI 代码更纯粹,业务逻辑更易于复用和测试。

步骤 1:定义数据模型

创建 model/Product.ets 文件,定义商品数据结构。使用 @Observed 装饰器,使其成为可被深度观察的对象。

typescript 复制代码
// model/Product.ets
import { Observed } from '@arkts.observer';
@Observed
export class Product {
  id: string;
  name: string;
  price: number;
  constructor(id: string, name: string, price: number) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}
步骤 2:创建分布式服务

创建 service/DistributedCartService.ets。这个单例服务将负责设备管理和 KV Store 的所有操作。

typescript 复制代码
// service/DistributedCartService.ets
import { BusinessError } from '@ohos.base';
import distributedDevice from '@ohos.distributedDevice';
import distributedData from '@ohos.data.distributedData';
import { Product } from '../model/Product';
import hilog from '@ohos.hilog';
import promptAction from '@ohos.promptAction';
const TAG = 'CartService';
const BUNDLE_NAME = 'com.example.distributedcart'; // 务必与你的App BundleName一致
const STORE_ID = 'shopping_cart_store';
export class DistributedCartService {
  private static instance: DistributedCartService = new DistributedCartService();
  private kvManager: distributedData.KVManager | null = null;
  private kvStore: distributedData.SingleKVStore | null = null;
  private deviceList: distributedDevice.DeviceInfo[] = [];
  public cartItems: Map<string, Product> = new Map();
  private constructor() {}
  public static getInstance(): DistributedCartService {
    return this.instance;
  }
  // --- 设备管理 ---
  async initDeviceManager(): Promise<void> {
    try {
      const deviceManager = distributedDevice.createDeviceManager(BUNDLE_NAME);
      deviceManager.on('deviceStateChange', (data) => {
        hilog.info(TAG, `Device state changed: ${JSON.stringify(data)}`);
      });
      deviceManager.on('deviceFound', (data) => {
        if (!this.deviceList.some(d => d.networkId === data.networkId)) {
          this.deviceList.push(data);
          hilog.info(TAG, `New device found: ${data.deviceName}`);
        }
      });
      deviceManager.on('discoverFail', (data) => {
        hilog.error(TAG, `Device discovery failed: ${JSON.stringify(data)}`);
      });
      hilog.info(TAG, 'Device Manager initialized.');
    } catch (err) {
      hilog.error(TAG, `Failed to init device manager. Code: ${(err as BusinessError).code}, Message: ${(err as BusinessError).message}`);
    }
  }
  async startDeviceDiscovery(): Promise<void> {
    try {
      const deviceManager = distributedDevice.createDeviceManager(BUNDLE_NAME);
      const extraInfo = {
        'targetPkgName': BUNDLE_NAME,
        'appName': 'DistributedCart'
      };
      deviceManager.startDeviceDiscovery(extraInfo);
      hilog.info(TAG, 'Start discovering devices...');
    } catch (err) {
      hilog.error(TAG, `Failed to start discovery. Code: ${(err as BusinessError).code}`);
      promptAction.showToast({ message: '启动设备发现失败' });
    }
  }
  stopDeviceDiscovery(): void {
    try {
      const deviceManager = distributedDevice.createDeviceManager(BUNDLE_NAME);
      deviceManager.stopDeviceDiscovery();
      this.deviceList = [];
      hilog.info(TAG, 'Stop device discovery.');
    } catch (err) {
      hilog.error(TAG, `Failed to stop discovery. Code: ${(err as BusinessError).code}`);
    }
  }
  getDiscoveredDevices(): distributedDevice.DeviceInfo[] {
    return this.deviceList;
  }
  // --- KV Store 管理 ---
  async initKVStore(context: Context): Promise<void> {
    if (this.kvStore) {
      hilog.info(TAG, 'KV Store already initialized.');
      return;
    }
    try {
      const kvManagerConfig: distributedData.KVManagerConfig = {
        context: context,
        bundleName: BUNDLE_NAME
      };
      this.kvManager = distributedData.createKVManager(kvManagerConfig);
      const options: distributedData.KVStoreOptions = {
        createIfMissing: true,
        encrypt: false,
        backup: false,
        autoSync: true,
        kvStoreType: distributedData.KVStoreType.SINGLE_VERSION,
        schema: undefined,
        securityLevel: distributedData.SecurityLevel.S1
      };
      this.kvStore = await this.kvManager.getKVStore(STORE_ID, options) as distributedData.SingleKVStore;
      hilog.info(TAG, 'Succeeded in getting KV store.');
      // 订阅数据变化
      this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
        hilog.info(TAG, `Data change received: ${JSON.stringify(data)}`);
        this.loadCartData(); // 重新加载数据以更新UI
      });
      await this.loadCartData(); // 初始化时加载数据
    } catch (err) {
      hilog.error(TAG, `Failed to init KV store. Code: ${(err as BusinessError).code}, Message: ${(err as BusinessError).message}`);
      promptAction.showToast({ message: '分布式数据初始化失败' });
    }
  }
  private async loadCartData(): Promise<void> {
    if (!this.kvStore) return;
    try {
      const entries = await this.kvStore.getEntries('product_') as distributedData.Entry[];
      this.cartItems.clear();
      entries.forEach(entry => {
        const product = JSON.parse(entry.value.toString()) as Product;
        this.cartItems.set(entry.key, product);
      });
      hilog.info(TAG, `Cart data loaded. Item count: ${this.cartItems.size}`);
    } catch (err) {
      hilog.error(TAG, `Failed to load cart data. Code: ${(err as BusinessError).code}`);
    }
  }
  // --- 购物车操作 ---
  async addProduct(product: Product): Promise<void> {
    if (!this.kvStore) {
      promptAction.showToast({ message: '数据服务未就绪' });
      return;
    }
    try {
      await this.kvStore.put(`product_${product.id}`, JSON.stringify(product));
      hilog.info(TAG, `Product added: ${product.name}`);
      promptAction.showToast({ message: `已添加: ${product.name}` });
    } catch (err) {
      hilog.error(TAG, `Failed to add product. Code: ${(err as BusinessError).code}`);
      promptAction.showToast({ message: '添加商品失败' });
    }
  }
  async removeProduct(productId: string): Promise<void> {
    if (!this.kvStore) {
      promptAction.showToast({ message: '数据服务未就绪' });
      return;
    }
    try {
      await this.kvStore.delete(`product_${productId}`);
      hilog.info(TAG, `Product removed: ${productId}`);
      promptAction.showToast({ message: '商品已移除' });
    } catch (err) {
      hilog.error(TAG, `Failed to remove product. Code: ${(err as BusinessError).code}`);
      promptAction.showToast({ message: '移除商品失败' });
    }
  }
}

第三部分:UI 界面开发

现在,UI 层变得非常简洁。它只需要调用 DistributedCartService 的方法,并响应其数据变化即可。

步骤 1:构建主界面

修改 pages/Index.ets,实现购物车列表和设备发现列表。

typescript 复制代码
// pages/Index.ets
import { DistributedCartService } from '../service/DistributedCartService';
import { Product } from '../model/Product';
import { distributedDevice } from '@ohos.distributedDevice';
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct Index {
  @State cartItems: Map<string, Product> = new Map();
  @State discoveredDevices: distributedDevice.DeviceInfo[] = [];
  @State isDiscovering: boolean = false;
  private cartService: DistributedCartService = DistributedCartService.getInstance();
  aboutToAppear(): void {
    // 初始化服务和加载数据
    this.cartService.initDeviceManager();
    this.cartService.initKVStore(getContext(this));
    this.refreshCart();
    this.refreshDeviceList();
  }
  aboutToDisappear(): void {
    // 页面销毁时停止发现,节省资源
    this.cartService.stopDeviceDiscovery();
  }
  refreshCart() {
    this.cartItems = new Map(this.cartService.cartItems);
  }
  refreshDeviceList() {
    this.discoveredDevices = this.cartService.getDiscoveredDevices();
  }
  build() {
    Column() {
      Text('分布式购物车').fontSize(24).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 10 })
      // 设备发现区域
      Column() {
        Row() {
          Text('设备列表').fontSize(18).fontWeight(FontWeight.Medium)
          Blank()
          Button(this.isDiscovering ? '停止发现' : '发现设备')
            .onClick(() => {
              if (this.isDiscovering) {
                this.cartService.stopDeviceDiscovery();
                this.isDiscovering = false;
                this.refreshDeviceList();
              } else {
                this.cartService.startDeviceDiscovery();
                this.isDiscovering = true;
                // 模拟定时刷新设备列表
                setInterval(() => {
                  this.refreshDeviceList();
                }, 2000);
              }
            })
        }.width('100%').padding(10)
        List({ space: 8 }) {
          ForEach(this.discoveredDevices, (device: distributedDevice.DeviceInfo) => {
            ListItem() {
              Row() {
                Text(device.deviceName).fontSize(16)
                Blank()
                Button('信任')
                  .onClick(() => {
                    // 此处省略认证逻辑,实际应用需要调用认证API
                    promptAction.showToast({ message: `已请求信任 ${device.deviceName}` });
                  })
              }.width('100%').padding(10)
            }
          })
        }.width('100%').layoutWeight(1)
      }.backgroundColor('#f1f3f5').borderRadius(12).margin({ horizontal: 16, vertical: 8 }).padding(12)
      // 购物车操作区域
      Row() {
        Button('添加商品 (手机)')
          .onClick(() => {
            const newProduct = new Product(Date.now().toString(), '华为手机', 6999);
            this.cartService.addProduct(newProduct);
            // 添加后手动刷新一次,因为数据同步是异步的
            setTimeout(() => this.refreshCart(), 500);
          })
        Button('添加商品 (平板)')
          .onClick(() => {
            const newProduct = new Product(Date.now().toString(), '华为平板', 4999);
            this.cartService.addProduct(newProduct);
            setTimeout(() => this.refreshCart(), 500);
          })
      }.justifyContent(FlexAlign.SpaceEvenly).width('100%').margin({ vertical: 10 })
      // 购物车列表
      Text(`购物车 (${this.cartItems.size})`).fontSize(18).fontWeight(FontWeight.Medium).margin({ top: 10 })
      List({ space: 8 }) {
        ForEach(Array.from(this.cartItems.entries()), (entry: [string, Product]) => {
          ListItem() {
            Row() {
              Column() {
                Text(entry[1].name).fontSize(16).fontWeight(FontWeight.Medium)
                Text(`¥${entry[1].price}`).fontSize(14).fontColor(Color.Red)
              }.alignItems(HorizontalAlign.Start)
              Blank()
              Button('删除')
                .onClick(() => {
                  this.cartService.removeProduct(entry[1].id);
                  setTimeout(() => this.refreshCart(), 500);
                })
            }.width('100%').padding(12)
          }
        }, (entry: [string, Product]) => entry[0]) // 使用唯一的ID作为key
      }.width('100%').layoutWeight(2).padding({ horizontal: 16 })
    }.width('100%').height('100%')
  }
}

第四部分:功能测试与验证

  1. 编译与安装: 使用 Full SDK 编译工程,并将生成的 HAP 包安装到两台 OpenHarmony 设备上。
  2. 授权: 在两台设备上首次启动应用时,系统会弹出权限请求框,请务必点击"允许"。
  3. 设备发现:
    • 在设备 A 上,点击"发现设备"按钮。
    • 等待几秒,设备 B 的名称应该会出现在设备 A 的列表中。
  4. 数据同步测试:
    • 添加: 在设备 A 上点击"添加商品 (手机)"。观察设备 A 的购物车列表出现新商品,并弹出"已添加"提示。3-5秒内,设备 B 的购物车列表也会自动刷新,显示相同的商品。
    • 删除: 在设备 B 上点击该商品旁的"删除"按钮。观察设备 B 的商品消失。3-5秒内,设备 A 的商品也会同步消失。
  5. 双向验证: 重复在设备 B 上添加、在设备 A 上删除的操作,确保数据同步是双向、可靠的。

第五部分:待完善和优化部分

  • 冲突解决: autoSync 模式下,如果设备离线操作,重新上线时系统会尝试合并。对于复杂场景,可关闭 autoSync,手动调用 sync 接口,并监听 syncComplete 回调处理冲突。
  • 性能优化: 当购物车商品数量巨大时,频繁的 put 和 UI 刷新可能影响性能。可考虑:
    • 批量更新: 将多个操作合并后一次性写入 KV Store。
    • LazyForEach: 对于超长列表,使用 LazyForEach 替代 ForEach 实现按需加载,提升滚动性能。
  • 数据安全: KV Store 支持在 KVStoreOptions 中设置 encrypt: true,对数据进行加密存储,保护用户隐私。
  • 用户体验增强:
    • 添加同步状态的视觉提示(如一个小图标表示正在同步)。
    • 实现离线模式,当设备无网络时,数据暂存本地,待网络恢复后自动同步。
    • 完善设备认证的 UI 流程,提供 PIN 码验证等更安全的组网方式。
相关推荐
筑梦之人11 小时前
Spark-3.5.7文档2 - RDD 编程指南
大数据·分布式·spark
爱笑的眼睛1112 小时前
HarmonyOS截屏与录屏API深度解析:从系统权限到像素流处理
华为·harmonyos
happy_king_zi12 小时前
RabbitMQ 是否也支持消费组
分布式·rabbitmq
Android疑难杂症13 小时前
鸿蒙Notification Kit通知服务开发快速指南
android·前端·harmonyos
兮动人14 小时前
PrettyZoo:优雅易用的 ZooKeeper 可视化管理工具
分布式·zookeeper·云原生·prettyzoo
回家路上绕了弯14 小时前
五分钟内重复登录 QQ 号定位:数据结构选型与高效实现方案
分布式·后端
BlackWolfSky15 小时前
鸿蒙三方库httpclient使用
华为·harmonyos·鸿蒙
爱笑的眼睛1115 小时前
HarmonyOS 分布式输入法开发指南:实现跨设备无缝输入体验
华为·harmonyos
夏文强15 小时前
HarmonyOS开发-系统AI视觉能力-图片识别
人工智能·华为·harmonyos
Random_index16 小时前
#HarmonyOS篇:管理组件拥有的状态
华为·harmonyos