1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/chinasoft6_ohos-distributed-ordering

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Это зеркальный репозиторий, синхронизируется ежедневно с исходного репозитория.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

[Zhongruan Jianguo] Распределенная система заказа еды для HarmonyOS

1. Описание проекта

  Этот проект можно использовать для заказа блюд в ресторанах во время коллективного питания или для объединенного заказа еды через мобильные приложения. Один человек открывает страницу меню и делится ею со всеми за столом. Все участники могут выбрать необходимые блюда с своих телефонов, и эти выборы будут одновременно синхронизированы на всех устройствах. Каждый участник сможет видеть выбранные другими блюда, и все вместе они будут поддерживать один общий список заказов. Это позволяет избежать необходимости передачи бумажных меню официантам или необходимости сообщать свои заказы одному человеку, который затем делает заказ.

Цель этого проекта — помочь разработчикам быстро освоить создание приложений на HarmonyOS, взаимодействие между JS и Java, вызов программных компонентов (PA) на разных устройствах и использование распределенной базы данных.## 2. Настройка среды разработки для HarmonyOS

  1. Установите DevEco Studio, подробнее см. Установка DevEco Studio.
  2. Настройте среду разработки DevEco Studio. Для работы требуется доступ к сети Интернет. В зависимости от ваших условий, выполните следующие шаги:   1. Если вы можете непосредственно подключиться к интернету, выполните скачивание SDK HarmonyOS.   2. Если прямой доступ к интернету недоступен, используйте прокси-сервер для подключения, см. настройку среды разработки.
  3. Программа должна быть запущена на реальном устройстве, поэтому вам потребуется получить сертификат:   1. Подготовьте ключ и запрос сертификата, см. Подготовка ключей и запросов сертификатов.   2. Получите сертификат для отладки, см. Запрос сертификата для отладки.## 3. Анализ структуры кода

Этот учебник будет содержать объяснение основного кода. Вы можете скачать полный код в конце руководства. Давайте сначала рассмотрим структуру всего проекта:

Проектная структура

  1. Java-data: содержит классы-оболочки для блюд и некоторые константы строк.
  2. Java-service: SharePageServiceAbility — это PA для взаимодействия JS и Java, где происходит вызов FA (MainAbility) между устройствами; DBInternalAbility — это ещё один PA для взаимодействия JS и Java, позволяющий JS обращаться к распределённой базе данных через Java.
  3. Java-utils: содержит утилиты для работы с базой данных.
  4. Js-common: components — папка для общих компонентов, imgs — папка для бизнес-изображений, json — папка для тестовых данных.
  5. Js-pages: detail — страница отображения списка блюд ресторана, index — страница отображения списка ресторанов, shoppingCart — страница расчёта.
  6. config.json: конфигурационный файл.

4. Открытие страницы заказа на разных устройствах##### 1. Запрос прав доступа

Для разработки программы требуется запрос следующих четырёх прав:     ohos.permission.GET_DISTRIBUTED_DEVICE_INFO: используется для получения списка устройств и информации о них в сети распределённого оборудования.     ohos.permission.DISTRIBUTED_DATASYNC: используется для обмена данными между различными устройствами.     ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE: используется для слежения за изменениями состояния устройств в сети распределённого оборудования.     ohos.permission.GET_BUNDLE_INFO: используется для запроса информации о других приложениях.1) В config.json добавьте следующий код запроса прав:

"reqPermissions": [
  {
    "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
  },
  {
    "name": "ohos.permission.DISTRIBUTED_DATASYNC"
  },
  {
    "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
  },
  {
    "name": "ohos.permission.GET_BUNDLE_INFO"
  }
]
  1. В методе onStart() класса MainAbility.java выполните запрос прав с использованием следующего основного кода:
private static final String PERMISSION_DATASYNC = "ohos.permission.DISTRIBUTED_DATASYNC";
private static final int MY_PERMISSION_REQUEST_CODE = 1;
private void requestPermission() {
	if (verifySelfPermission(PERMISSION_DATASYNC) != IBundleManager.PERMISSION_GRANTED) {
		if (canRequestPermission(PERMISSION_DATASYNC)) {
			requestPermissionsFromUser(new String[] {PERMISSION_DATASYNC}, MY_PERMISSION_REQUEST_CODE);
		}
	}
}
2. Вызов PA (Java API) из FA (JS API)

Страница отображения меню ресторана detail (страница заказа), при нажатии кнопки "Поделиться" в верхней части экрана вызывается SharePageServiceAbility для взаимодействия с Java.

const ABILITY_TYPE_EXTERNAL = 0;
const ABILITY_TYPE_INTERNAL = 1; // syncOption(Optional, default sync): 0-Sync; 1-Async
const ACTION_SYNC = 0;
const DISTRIBUTE_PAGE = 1000;

async function shareToOthers() {
	var actionData = {};
	actionData.restaurantId = this.restaurantId;

	var action = {};
	action.bundleName = 'com.example.ordering';
	action.abilityName = 'com.example.ordering.service.SharePageServiceAbility';
	action.messageCode = DISTRIBUTE_PAGE;
	action.data = actionData;
	action.abilityType = ABILITY_TYPE_EXTERNAL;
	action.syncOption = ACTION_SYNC;
}

var result = await FeatureAbility.callAbility(action);

3. Обнаружение устройств и открытие страницы миграции устройств

В SharePageServiceAbility открывается страница MainAbility для всех коллег.

List<DeviceInfo> deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
``````markdown
if(deviceInfoList != null && deviceInfoList.size() > 0){
	for (DeviceInfo info : deviceInfoList){
		Intent intent = new Intent();
		Operation operation = new Intent.OperationBuilder()
			.withDeviceId(info.getDeviceId())
			.withBundleName("com.example.ordering")
			.withAbilityName("com.example.ordering.MainAbility")
			.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
			.build();
		intent.setParam(Constant.INIT_PAGE_PARAM, Constant.PAGE_DETAIL);
		intent.setParam(Constant.INIT_RESTAURANTID, param.getRestaurantId());
		intent.setOperation(operation);
		startAbility(intent);
	}
}

4. Как маршрутизировать на страницу меню

  1. В методе start в MainAbility.java, передаются ресторан ID и необходимая страница для JS-страницы:
Строковое значение init_page = intent.getStringParam(Constant.INIT_PAGE_PARAM);
Строковое значение init_restaurantId = intent.getStringParam(Constant.INIT_RESTAURANTID);
Если(init_page != null && !init_page.isEmpty()){
	IntentParams params = new IntentParams();
	params.setParam(Constant.INIT_PAGE_PARAM, Constant.PAGE_DETAIL);
	params.setParam(Constant.INIT_RESTAURANTID, init_restaurantId);
	setPageParams(null, params);
}
DBInternalAbility.register(this);
setInstanceName("default");
  1. На главной странице модуля js-default, маршрут направляется на страницу деталей заказа:
onReady(){
	Если(этот.init_page === "detail"){
		router.push({uri: 'pages/detail/detail', params: {restaurantId: этот.init_restaurantId}});
	}
}

5. Обработка данных распределенной базы данных

1. Запрос на права доступа

Запрос на права доступа аналогичен предыдущему шагу, поэтому нет необходимости повторять его.

2. Создание распределённой базы данных (DbHelper)

Для создания распределённой базы данных сначала создаётся экземпляр управления распределённой базой данных KvManager.

Приватный метод KvManager createManager() {
	KvManager kvmanager = null;
	try {
		KvManagerConfig config = new KvManagerConfig(context);
		kvmanager = KvManagerFactory.getInstance().createKvManager(config);
	} catch (KvStoreException exception) {
		HiLog.info(LABEL_LOG, "Произошло исключение");
	}
	return kvmanager;
}
```
Класс `KvManager` успешно создан, после чего с его помощью создается распределенная база данных `SINGLE_VERSION`. Методы создания следующие:

```java
private SingleKvStore createDb(KvManager kvmanager) {
	if (kvmanager == null) return null;
	SingleKvStore kvStore = null;
	try {
		Options options = new Options();
		options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
		kvStore = kvmanager.getKvStore(options, STORE_ID);
	} catch (KvStoreException exception) {
		HiLog.info(LABEL_LOG, "Произошла ошибка");
	}
	return kvStore;
}
```

Распределенная база данных типа `SINGLE_VERSION` представляет собой способ хранения данных локально в виде отдельных KV записей, где каждому ключу соответствует максимум одна запись. Любое изменение данных пользователями происходит непосредственно в этой записи, вне зависимости от того, был ли этот изменения уже отправлен.

Далее следует подписка на изменения данных в распределенной базе данных. Методы подписки представлены ниже:

```java
private void subscribeDb(SingleKvStore kvStore) {
	if (kvStore == null) return;
	KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
	kvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_REMOTE, kvStoreObserverClient);
}
```Распределённая база данных поддерживает подписку как на удалённые, так и на локальные изменения данных. В примере используется подписка на удалённые данные, а для подписки на локальные данные используется параметр SUBSCRIBE_TYPE_LOCAL. Также поддерживаются подписки на все изменения, используя параметр SUBSCRIBE_TYPE_ALL.##### 3. Поиск, вставка и удаление данных

1) Вставка данных

Сначала создаются ключ (key) и значение (value) для распределенной базы данных, затем данные вставляются в базу данных через метод `putString`, пример использования приведен ниже:

```java
private void writeData(String key, String value) { 
    if (key == null || key.isEmpty() || value == null || value.isEmpty()) { 
        return; 
    } 
    singleKvStore.putString(key, value); 
}

public void writeData(String name, int count, int deskNum, String restaurantId, String dishId) {
    if(kvStore == null) return;
    if (name == null || name.isEmpty()) {
        return;
    }
    kvStore.putString(name + restaurantId, name + ";" + count + ";" + deskNum + ";" + Dish.NO_VALUE + ";" + restaurantId + ";" + dishId);
}
```

2. Запрос данных  
Запрос выполняется по ключу (ключ). Если указывается конкретный ключ, то возвращаются данные, соответствующие этому ключу; если ключ не указан, то возвращаются все данные. Пример кода запроса приведён ниже (вместе с кодом запроса содержится часть бизнес-логики):```java
public String queryData(String restaurantId) {
    this.restaurantId = restaurantId;
    if (kvStore == null) return null;
    List<Entry> entryList = kvStore.getEntries("");
    String json = "[";
    try {
        for (Entry entry : entryList) {
            String name = entry.getKey();
            String[] values = entry.getValue().getString().split(";");
            if (restaurantId == null || restaurantId.equals(values[4])) {
                json += "{"
                        + "\"name\":\"" + values[0] + "\","
                        + "\"count\":" + values[1] + ","
                        + "\"deskNum\":" + values[2] + ","
                        + "\"address\":\"" + values[3] + "\","
                        + "\"restaurantId\":\"" + values[4] + "\","
                        + "\"id\":\"" + values[5]
                        + "}";
            }
        }
    } catch (KvStoreException exception) {
        HiLog.info(LABEL_LOG, "Значение должно быть строкой");
    }
}
```

```markdown
3) Удаление данных  
Для удаления данных можно использовать метод `delete()`, передав ему заранее определённый ключ (`key`). Пример кода:
``````java
public void deleteData(String key) {
    if(kvStore == null) return;
    if (key.isEmpty()) {
        return;
    }
    kvStore.delete(key + restaurantId);
    HiLog.info(LABEL_LOG, "Удаление ключа key=" + key);
}
```

4) Удаление базы данных  
Для удаления базы данных используется метод `deleteKvStore()`. Этот метод требует передачи заранее определённого параметра `STORE_ID`. Пример кода:

```java
public void deleteDb(){
    if(kvmanager == null) return;
    kvmanager.closeKvStore(kvStore);
    kvmanager.deleteKvStore(STORE_ID);
}
```

### 4. Синхронизация распределённой базы данных
Перед синхронизацией данных необходимо получить список устройств в текущей сети, а затем выбрать способ синхронизации (PULL_ONLY, PUSH_ONLY, PUSH_PULL). В качестве примера рассмотрим синхронизацию в режиме PUSH_PULL:

```java
public void syncData(String restaurantId) {
    this.restaurantId = restaurantId;
    if(kvmanager == null || kvStore == null) return;
    List<DeviceInfo> deviceInfoList = kvmanager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);
    List<String> deviceIdList = new ArrayList<>();
    for (DeviceInfo deviceInfo : deviceInfoList) {
        deviceIdList.add(deviceInfo.getId());
    }
    HiLog.info(LABEL_LOG, "Количество устройств= " + deviceIdList.size());
    if (deviceIdList.size() == 0) {
        String result = queryData(this.restaurantId);
        if(kvStoreLishner != null) {
            kvStoreLishner.updataUI(result);
        }
        return;
    }
    kvStore.registerSyncCallback(new SyncCallback() {
        @Override
        public void syncCompleted(Map<String, Integer> map) {
            String result = queryData(getRestaurantId());
            if(kvStoreLishner != null) {
                kvStoreLishner.updataUI(result);
            }
            kvStore.unRegisterSyncCallback();
        }
    });
    kvStore.sync(deviceIdList, SyncMode.PUSH_PULL);
}
```Вышеуказанный код также включает некоторые бизнес-логики помимо синхронизации данных.
```#### 5. Синхронизация данных страницы заказа

##### 1. Открытие FA-страницы между устройствами и начальная синхронизация данных
1. При первоначальном открытии страницы деталей заказа происходит синхронизация уже сделанных заказов на других устройствах в сети. В методе жизненного цикла `onShow` вызывается метод `subscribeInternal`, чтобы подписаться на событие `PA-DBInternalAbility`. В методе жизненного цикла `onHide` вызывается метод `unsubscribeInternal`, чтобы отписаться от события `PA-DBInternalAbility`. Конкретный код представлен ниже:

```javascript
subscribeInternal: async function() {
    var that = this;
    var actionData = {};
    actionData.restaurantId = this.restaurantId;
    var action = {};
    action.bundleName = 'com.example.ordering';
    action.abilityName = 'com.example.ordering.service.DBInternalAbility';
    action.messageCode = ACTION_MESSAGE_CODE_SUBSCRIBE;
    action.data = actionData;
    action.abilityType = ABILITY_TYPE_INTERNAL;
    action.syncOption = ACTION_SYNC;
    var result = await FeatureAbility.subscribeAbilityEvent(action, function (callbackData) {
        var callbackJson = JSON.parse(callbackData);
        that.queryData = JSON.parse(callbackJson.data.abilityEvent);
        that.initDetailAndCart();
    });
},

unsubscribeInternal: async function() {
    var action = {};
    action.bundleName = 'com.example.ordering';
    action.abilityName = 'com.example.ordering.service.DBInternalAbility';
    action.messageCode = ACTION_MESSAGE_CODE_UNSUBSCRIBE;
    action.abilityType = ABILITY_TYPE_INTERNAL;
    action.syncOption = ACTION_SYNC;

    var result = await FeatureAbility.unsubscribeAbilityEvent(action);
}
```

2. Обработка двух типов событий  подписки и отмены подписки  осуществляется в методе `onRemoteRequest` компонента `PA-DBInternalAbility`. Конкретный код представлен ниже:
``````java
case SUBSCRIBE: {
    remoteObjectHandler = data.readRemoteObject();
    String zsonStr = data.readString();
    Dish param = new Dish();
    try {
        param = ZSONObject.stringToClass(zsonStr, Dish.class);
    } catch (RuntimeException e) {
        HiLog.error(LABEL, "преобразование не удалось.");
    }
    dbHelper.syncData(param.getRestaurantId());
    break;
}

// Отмена подписки, освобождение удаленного обработчика
case UNSUBSCRIBE: {
    remoteObjectHandler = null;
    break;
}
```3. Метод синхронизации данных dbHelper.syncData в базе данных уже был рассмотрен выше. После того как данные были синхронизированы, метод проверяет данные распределенной базы данных через kvStoreListener.updateUI(result) и обновляет данные интерфейса. Конкретная реализация updateUI приведена ниже:

```java
public void updateUI(String result) {
    try {
        MessageParcel data = MessageParcel.obtain();
        MessageParcel reply = MessageParcel.obtain();
        MessageOption option = new MessageOption();
        Map<String, Object> zsonEvent = new HashMap<>();
        zsonEvent.put("abilityEvent", result);
        data.writeString(ZSONObject.toZSONString(zsonEvent));
        if (remoteObjectHandler != null) {
            remoteObjectHandler.sendRequest(100, data, reply, option);
        }
        reply.reclaim();
        data.reclaim();
    } catch (RemoteException e) {
    }
}
```

4. PA-DBInternalAbility следует зарегистрировать в методе onStart класса MainAbility с помощью DBInternalAbility.register и отменить регистрацию в методе onStop с помощью DBInternalAbility.unregister. Конкретные коды регистрации и отмены регистрации представлены ниже:

```java
/**
 * Регистрация внутреннего способности.
 */
public static void register(AbilityContext abilityContext) {
    instance = new DBInternalAbility();
    instance.onRegister(abilityContext);
}
```### Шестое. Конечный результат реализации
![Результат реализации](https://images.gitee.com/uploads/images/2021/0727/110152_3645f0ba_8627638.gif "distribute_demo.gif")

---

```java
private void onRegister(AbilityContext abilityContext) {
    dbHelper = new DbHelper(abilityContext, kvStoreListener);
    dbHelper.initDbManager();
    this.abilityContext = abilityContext;
    this.setInternalAbilityHandler((code, data, reply, option) -> {
        return this.onRemoteRequest(code, data, reply, option);
    });
}

/**
 * Отмена регистрации внутренней способности.
 */
public static void unregister() {
    instance.onUnregister();
}

private void onUnregister() {
    dbHelper.deleteDb();
    abilityContext = null;
    this.setInternalAbilityHandler(null);
}
```

#### 2. Операции на странице FA одного устройства и синхронизация данных других устройств

При создании базы данных мы подписываемся на изменения распределённой базы данных, поэтому каждый раз при изменении базы данных вызывается `kvStoreListener.updateUI` для обновления данных интерфейса. Конкретный код представлен ниже:

```java
public void initDbManager() {
    kvmanager = createManager();
    kvStore = createDb(kvmanager);
    subscribeDb(kvStore);
}
```### Седьмое. Обзор и выводы
&emsp;&emsp;&emsp;&emsp;В данной статье мы подробно рассмотрели пример многопользовательского онлайн-заказа еды с использованием распределённой системы, чтобы помочь вам быстро освоить разработку приложений на HarmonyOS, взаимодействие JS-Java, получение переносимых устройств, открытие страниц переносимого устройства и использование распределённой базы данных. Мы детально объяснили, как осуществляется совместное использование данных и страниц между несколькими устройствами, что является ключевым моментом для вашего обучения.&emsp;&emsp;&emsp;&emsp;Особенно следует отметить, что для запуска требуется минимум два смартфона, находящихся в одной распределённой сети. Это можно достичь следующими шагами:

&emsp;&emsp;&emsp;&emsp;1) Все устройства должны быть подключены к одному сетевому соединению;
&emsp;&emsp;&emsp;&emsp;2) На всех устройствах должна быть авторизованна одна учётная запись Huawei;
&emsp;&emsp;&emsp;&emsp;3) На всех устройствах должно быть активировано "Настройки -> Дополнительно -> Множество устройств";
&emsp;&emsp;&emsp;&emsp;4) На всех устройствах должно быть включено Bluetooth

### Восьмое. Код для справки
[https://gitee.com/chinasoft6_ohos/distributed-ordering](https://gitee.com/chinasoft6_ohos/distributed-ordering)

Авторы: Чень Цяоинь, Цю Вейкан
```

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Описание недоступно Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://gitlife.ru/oschina-mirror/chinasoft6_ohos-distributed-ordering.git
git@gitlife.ru:oschina-mirror/chinasoft6_ohos-distributed-ordering.git
oschina-mirror
chinasoft6_ohos-distributed-ordering
chinasoft6_ohos-distributed-ordering
master