今日、テープの
記事を見て
、自分のプロジェクトについて、自分のプロジェクトについて2、3行書きたいと思いました。
一般的に、私はしばらくの間、iOSの技術顧問として働いていました。Androidプログラマーは、Django / Yii2 /独自のコードで多くのAPIを使用していました。 そして、REST APIを操作するためのツールの側面から見て、Qtに似たものを追加することにしました。 Qtモデルを使用してRESTを操作するための通常のツールはありませんでした。
すぐに言ってやった。 下の図で、結果のスキームとその下は、実際にはアイデア、アーキテクチャ、および使用のための簡単な指示の説明です。
したがって、ここで説明します。
- アイデアと機能
- 建築
- 使用例
- ソースコードとサンプルアプリケーション
アイデアと機能
基本的に、通常設計されたREST APIはすべて、HTTPリクエストを受信し、リスト/単一データオブジェクトをJSON / XMLでクライアントに送信します。
一方、Qtには、データの読み取りと変更の両方の問題を解決できるモデルのメカニズムが長い間あり、モデルの基本クラスの対応する関数を再定義するだけで済みます。
これらのメッセージに基づいて、ライブラリは次の要件を満たす必要があると判断しました。
- C ++およびQMLから入手可能。
- リスト項目(ListView、GridViewなど)の新しいページを自動的にロードするためのfetchMore()およびcanFetchMore()メソッドのサポート(再定義)を備えたQAbstractListModelに基づきます。
- JSON / XML形式のデータの受信と解析。
- ページネーションのパラメータ化:ページごと(ページごと)、制限/シフト(制限/オフセット)ごと、カーソル(カーソル)ごと。
- ソートのパラメーター化。
- フィルタリングのパラメーター化。
- リストデータの返されたフィールドのリストのパラメーター化。
- 認証サポート。
- モデルなしでAPIを使用する;
- 「リスト->アイテムの詳細な説明」などのナビゲーションのためのデータの遅延ロード(遅延ロード)のサポート。
- モデルと特定のAPIメソッドの分離、および最終アプリケーションでのAPIの実装の容易さ。
- 各要素(データ操作用)のAPIにキーフィールドが存在することを要求します。
- 同じアプリケーション内の多くの外部APIのサポート。これにより、さまざまなモデルとAPIクラスが互いに可能な限り独立します。
- 機能的な基本モデルの存在と独自のモデルを作成する能力。
要件の開発では、
Yii2-RESTや
Django RESTフレームワークなどのサーバー側API作成ツールに焦点を当てました。 私の意見では、これらは完全に異なる世界から来ているだけでなく、RESTサービスを作成するための最も機能的な無料のソリューションであり、ドキュメントを分析し、テストプロジェクトを書くときに、サーバーでRESTを整理するためのさまざまなアプローチに関するデータを受け取りました。
建築
したがって、上記のように、すべてがQAbstractListModelを中心に展開します。これは、Qt C ++ / QMLからデータにアクセスするネイティブな方法だからです。 詳細に移りましょう。
上記の図によると、2つの基本クラスがあります:APIBase(QObjectの子孫)とBaseRestListModel(QAbstractListModelの子孫)。
- APIBaseは、サーバーで直接動作する結果のAPIの基本クラスです。 ネットワークに関するすべての作業は、その相続人にカプセル化する必要があります。
- BaseRestListModel-このクラスは抽象内部クラスであり、プログラマがアプリケーションで使用することは決してありません。 このクラスは、APIクラスを操作するために必要なすべてのプロパティとメソッドを記述します。 次に、これらのクラスは、プログラマが処理する必要がある2つのクラス、AbstractJsonRestListModelおよびAbstractXmlRestListModelを継承します。 タイトルからわかるように、これらのクラスはjson / xmlデータパーサーです。 新しいデータ形式のパーサーを実装する必要がある場合(csv?=))、階層内のこれら2つ以上と同様にすべてを実行し、自動的に開始します。
APIBaseクラスのプロパティ:
- accept-クラスAbstractJsonRestListModelおよびAbstractXmlRestListModel、それぞれapplication / jsonおよびapplication / xmlによって設定された、要求されたデータ形式。
- acceptHeaderは、acceptプロパティのヘッダー名です。 デフォルトでは、その論理タイトルは「Accept」と呼ばれます。 このプロパティと以前のプロパティの意味は、Yii2とDjango、およびおそらく他のサービスの両方が、データベースからjson / xmlにデータをその場でシリアル化できることです。
- baseUrlは単にベースURLであり、呼び出されたAPIメソッドとパラメーターの名前が最後に追加されます。
- authToken-認可トークン、全文(まあ、「Bearer 8aef452ee3b32466209535b96d456b06」);
- authTokenHeader-トークンを持つヘッダーの名前。デフォルトは「Authorization」です。
このプロパティのセットは、原則として、どのサービスでも機能するのに十分です。 各プロパティ自体は、派生クラスおよびQMLで使用できます(これらはすべてQ_PROPERTYであるため)。
APIを操作するための独自のクラスを作成するには、APIBaseを継承し、少なくともhandleRequestメソッドを実装する必要があります。また、上記のプロパティ、モデルのパラメーター(以下)、およびprotectedメソッドを使用してサーバーからデータを受信するために必要なすべてのメソッドを取得、投稿、配置する必要があります、deleteResource、head、options、patch(すべてHTTPプロトコルの同じメソッドに対応)。
それだけです。データを受信するメソッド内には、モデルから転送されたパラメーターを解析するためのコードが必要です(アプリケーションから読み取ります)。それは技術の問題であり、QUrl / QUrlQueryを使用してサーバーに正しいリクエストを送信するだけです。 結果は、QNetworkReplyへのポインターであり、QNetworkReplyは基本クラスに戻り、すでに完了信号へのサブスクリプションがあります。
クラスAPIとモデルを使用するためのさまざまなシナリオを考えてみましょう。
1. handleRequestと既製の読み取り専用モデル
このシナリオは、独自のデータモデルが不要で、準備が必要な場合に使用します。 ライブラリには、JsonRestListModelとXmlRestListModelという2つのモデルがあります。
これらのモデルは両方とも読み取り専用であり、C ++ / QMLからすぐに使用できます。
ReadOnlyモデルを使用するには、APIクラスでhandleRequestメソッドを実装する必要があります。そのインターフェイスは次のとおりです。
virtual QNetworkReply *handleRequest(QString path, QStringList sort, Pagination *pagination, QVariantMap filters = QVariantMap(), QStringList fields = QStringList(), QString id = 0)
pathはAPIメソッド、sortは並べ替えパラメーター、paginationはページングパラメーターオブジェクト、filtersはフィルターパラメーター、fieldsは返されるフィールドのリスト、idはレコードの一意の識別子です。
各ReadOnlyモデルは、次の形式でAPIへのアクセスを実装します。
QNetworkReply *JsonRestListModel::fetchMoreImpl(const QModelIndex &parent) { Q_UNUSED(parent) return apiInstance()->handleRequest(requests()->get(), sort(), pagination(), filters(), fields()); }
少し先を見て、このようなモデルをQMLで使用する方法を紹介します。
... MyApi { id: myApi } JsonRestListModel { id: jsonSampleModel api: myApi
既製のモデルを使用すると、APIクラスのみを実装でき、モデルの継承について心配する必要はありません。 もちろん、次のシナリオのように、ここではAPIクラスに完全な機能が必要です。
2.独自のモデルを書く
ReadOnlyモデルが私たちに合わない場合、AbstractJsonRestListModelとAbstractXmlRestListModelを継承し、データを操作するために必要なすべてのメソッドを使用して独自のモデルを作成できます。 このタイプのモデルについては、使用例で詳しく説明します。
データ操作のために、2つのオプションを使用できます。
- APIで作成/更新するメソッドを作成し、直接使用します。
- APIで、サーバーに変更要求を送信するためのメソッドのみを作成し、モデルにデータ準備を実装します。
ここでは、制限を導入しないことにしましたが、2番目のオプションはより正確です。 理由を説明します。 各モデル要素はRestItemクラスのインスタンスであり、モデル自体には次のようなヘルパーメソッドがあります。
- RestItem BaseRestListModel :: createItem(QVariantMap value)-QVariantMapで受信および折り畳まれたJSON / XML応答から新しいモデルオブジェクトを作成します。
- RestItem BaseRestListModel :: findItemById(QString id)-IDでモデル要素を検索します。 データ更新メソッドでこのメソッドを呼び出して新しいオブジェクトを受信することにより、変更を加え、変更されたオブジェクトをモデルに(updateItemメソッドのように)配置し、最後に更新されたオブジェクトを使用してサーバーに送信できます。
したがって、モデルには、APIクラスには存在しないモデル要素を使用した直接作業のメカニズムがあり、サーバーでの作業に最低限必要な機能はAPIクラスに残り、すべてのデータの準備/処理はモデルで行われます。
RestItemクラス自体は、QMLで利用可能なキー/値を表すために、標準のQtメソッドであるdata()およびroleNames()モデルメソッドに必要です。
3. APIクラスを直接使用する
最後に、APiクラスで作業のすべてのロジックを記述したため、モデルなしで実行できます。このため、replyFinishedメソッドを再定義し、APIを介して直接リクエストを行うだけで十分です。
うーん...私はもちろんシナリオの説明に夢中になりました...私たちはさらにモデルに行きます。 先ほど言ったように、すべてのモデルの基本クラスはBaseRestListModelです。 実際、この基本クラスはほとんどすべての作業を行います。
したがって、クラスプロパティのリスト:
- APIBase * apiは、モデルのAPIオブジェクトへのポインターです。 ポインタ。良い方法では、1つのAPIオブジェクトがアプリケーション内の1つのサービスと連携する必要があるためです。 ポインターは、QML(上記)とC ++の両方から指定できます。
- QStringList sort-ソートオプション。 これはQStringListであり、1行= 1の並べ替えフィールド、サーバーでの処理方法-気にしません。 例:['-id'、 'name']-この場合、サーバーはidフィールドの降順および名前でデータをソートすると想定されます。
- ページネーション*ページネーション-ページネーションオブジェクトへのポインタ。 ページネーションは、このモデルのページネーション状態を設定および保存する別個のクラスです。 彼についてもう少し低い。
- QVariantMapフィルター-フィルターを含む配列、内部-QVariantMap、QMLの場合、「{'isArchive': '0'}」、つまり「isArchiveフィールドはゼロでなければなりません」のように設定されます。 ">、<、> =、<="など、フィールド値に何でも渡すことができます-主なことは、サービスがそのようなコマンドを理解できることです。
- QStringListフィールド-以降 RESTサービスはすべてのフィールドを返すわけではありませんが、携帯電話のリストの場合、20のフィールドではなく、TEXTおよびBLOBタイプもあり、リストで使用されるフィールドは2〜3であることが重要です。フィールドを取得します。
- QString idField-キーフィールドの名前;原則として、データを変更し、各レコードの拡張(詳細)情報を取得するために操作が実行されます。
- QString fetchDetailLastId-拡張属性が要求された最後のレコードのキー。
- DetailsModel * detailsModel-仕様。 1つの(最後に要求された)レコードの拡張属性を格納するモデル。 このモデルは、詳細な記録情報のページで使用できます。 ええと、たとえば-YouTube、VK、Picabuのフィードに目を通します...投稿をクリックします-コメントがそこにダウンロードされ、全文、ビデオ情報、その他のおばあちゃん。
- LoadingStatus loadingStatus-このプロパティを介して、モデルは現在の状態を報告し、それに基づいて、アプリケーションの状態と内部のアニメーションを調整できます。 値は次のとおりです。Idle、IdleDetails、RequestToReload、FullReloadProcessing、LoadMoreProcessing、LoadDetailsProcessing、Error。
- loadingErrorString、loadingErrorCode-最後のリクエストのエラーメッセージが保存されます。
- count-モデル内の現在のエントリ数。
ここでも、すべてのプロパティはQ_PROPERTYです。
プロパティに加えて、このクラスから継承された各モデルには次のメソッドがあります。
- void reload()-モデルデータを完全にリロードします。
- void fetchDetail(QString id)-レコードごとに詳細情報を取得するメソッド。DetailsModelモデルに入力し、* detailsModelプロパティを通じてアクセスできます。
- void requestToReload()-実際の要求を実行せずに、RequestToReloadで除算された状態のみを変更します。 追加を実行する場合に必要です。 GUIの状態変更と実際の要求の間のアクション。 ユーザーモデルでオーバーライドされたfetchDetailImplメソッドを使用します。
- void forceIdle()-他からアイドル状態のモデルを返し、ブートプロセスを終了します。
- bool canFetchMore()-ページネーションオブジェクトと現在の状態に基づいて、モデルを返し、さらに受信するデータがあるかどうかに関する情報を返します。 ListView、GridView、PathViewで自動的に使用されるユーティリティメソッド。
- void fetchMore()-データを取得する実際のメソッド。 ページネーションの状態を考慮し、fetchMoreImplという名前で再定義されたユーザーモデルからデータを取得するメソッドを呼び出します。
- int rowCount()-現時点でのモデル内のレコードの数。
AbstractJsonRestListModelまたはAbstractXmlRestListModelから継承した作業モデルを作成するには、いくつかのメソッドも実装する必要があります。
- virtual QNetworkReply * fetchMoreImpl(const QModelIndex&parent)-APIからの新しいデータの実際の受信を実装するメソッド。
- virtual QNetworkReply * fetchDetailImpl(QString id)-レコードに関する詳細情報の受信を実装するメソッド。
- 仮想QVariantMap preProcessItem(QVariantMapアイテム)-このメソッドを使用すると、JSON / XMLから受信してからモデルに追加する前に各レコードを前処理できます。 一般に、データを準備するタスクはバックエンド開発者のタスクですが、たとえば5つの異なる形式で日付フィールドを表示する必要がある場合は、ネットワーク上で+5フィールドをドライブするよりもクライアントで前処理する方が適切です。
- 仮想QVariantList getVariantList(QByteArrayバイト)-JSON / XML解析メソッド、AbstractJsonRestListModelおよびAbstractXmlRestListModelで既に再定義されています。アプリケーションで覚えておく必要はありません。
- 仮想QVariantMap getVariantMap(QByteArrayバイト)-前のものと似ていますが、構文解析するオブジェクトのリストではなく、1つのオブジェクトです。
もちろん、このモデルにはあらゆるものが含まれていますが、ソースでこれらすべてを確認できます。個々のニーズに合わせてモデルを理解し、書き直すのに十分なコメントがあります。 すべての内線 保護されたセクションのメソッド。
あなたが言ったように、2つの特定のクラスがモデルに関連付けられています-PaginationとDetailsModel。
DetailsModelを使用すると、原則としてすべてがシンプルになります。 アプリケーションでリスト項目をクリックすると、データが要求され、このモデルでデータが書き込まれ、アプリケーションへのポインターが与えられます。 アプリケーションでは、真実を少し歪め、1つの要素で非対話型のListViewを作成する必要があります。これには、デリゲートと詳細モデルへのポインターが必要です。これにより、「詳細情報のページ」が取得されます。
ページネーションも問題になりません。 このクラスは、ページネーションパラメーターのみを定義し、モデルの現在の状態を保存します。 すべてはプロパティのセットによっても設定されます:
- PaginationPolicyポリシー-値None、PageNumber、LimitOffset、Cursor、Infinityを取ります。 フィールドの意味を不必要に説明すると思います。
- ポリシーPageNumberの場合、プロパティはperPage、currentPage / currentPageHeader(readOnlyはサーバーのヘッダーからのウィービングページ)、pageCount / pageCountHeader(サーバーの対応するヘッダーからも読み取ります)に設定されます。 つまり、perPageを設定し、サーバーからページ数と現在のページを取得し、canFetchMoreで使用します。
- ポリシーLimitOffsetおよびCursorには、読み取り専用フィールドtotalCount / totalCountHeaderがあります。 つまり、レコードの総数に関する情報をサーバーから取得します。
- LimitOffsetには、制限とオフセットを設定します。
- カーソルの場合、cursorQueryParamとcursorValueを設定します。
それだけです。モデル自体は、ListViewとともに新しいデータのロードを台無しにし、最大値に達するとロードを停止します。 カウント。
ああ、ReadOnlyモデルとQMLで使用されるRequestsクラスがまだあります。 ソースを見てください、すべてがそこで簡単です)
それは私が得たアーキテクチャについてです。 もちろん、ライブラリを作成し、すぐに実行に移そうとしたので、コンパイルして表示できるデモアプリケーションを作成しました。 以下にその例を示します。ライブラリを使用する手順を分析します。
使用例
Yii2には1つの小さなプロジェクトがあります。それは...にありますが、どのプロジェクトかはわかりません。
そのため、このプロジェクトでは、実際にデモを開発するときに使用したいくつかのAPIメソッドを実装しました。
以下は、使用されるAPIメソッドとそれらが返すデータです。
/ v1 /カテゴリ [{ "id": 1, "sourceServiceId": 2, "categoryName": "", "categoryCode": "aktsii", "categoryIdentifier": "0", "parentCategoryIdentifier": "0", "categoryAdditionalInfo": "0", "isActive": 1 }, { "id": 2, "sourceServiceId": 2, "categoryName": "", "categoryCode": "kupony", "categoryIdentifier": "28", "parentCategoryIdentifier": "28", "categoryAdditionalInfo": "https://blizzard.kz/kuponator/categ/28", "isActive": 1 } , ...]
/ v1 /クーポン [{ "id": 1, "sourceServiceId": 1, "cityId": 1, "createTimestamp": "2015-03-12 14:01:57", "lastUpdateDateTime": "2016-10-20 03:54:47", "recordHash": "e7b01c1a69bc66e1f1a62d8fcb0825de", "title": "Home Club", "shortDescription": " , , , ", "longDescription": " – . , – - Home Club. 50%! , , . ! ", "conditions": " <p class="e-condition__text">:</p> <ul class="b-conditions-list"> <li class="e-condition"> - Home Club.</li> <li class="e-condition"> <strong></strong>: 50% - 1 500 . 3 000 .</li> <li class="e-condition"> ( ).</li> <li class="e-condition"> .</li> <li class="e-condition"> 10 .</li> <li class="e-condition"> <strong>VIP- . VIP- .</strong> </li> <li class="e-condition"> .</li> <li class="e-condition"> <strong> :</strong><br> +7 (727) 308-23-63,<br> +7 (747) 841-42-51,<br> +7 (701) 985-90-72.</li> <li class="e-condition"> <strong> .</strong> </li> <li class="e-condition"> 2 , ( ).</li> <li class="e-condition"> , «».</li> <li class="e-condition"> Home Club : , , , Home Club.</li> <li class="e-condition"> , , .</li> <li class="e-condition"> <strong> .</strong> </li> <li class="e-condition"> 12 2015 . ().</li> <li class="e-condition"> <span hashstring="deal_refunds_policy" hashtype="content"> </span> </li> <li class="e-condition"> <span hashstring="deal_standard_conditions" hashtype="content"> </span> </li> </ul> <p class="e-offer__features"></p> <ul class="b-offer__features-list"> <li class="e-offer__feature "> , , , Home Club </li> <li class="e-offer__feature "> :<br> +7 (727) 308-23-63<br>+7 (747) 841-42-51<br>+7 (701) 985-90-72<br> </li> <li class="e-offer__feature "> :<br> : </li> </ul>", "features": " <p class="e-offer__features">:</p> <ul class="b-offer__features-list"> <li class="e-offer__feature">Home Club , , .</li> <li class="e-offer__feature"> 10 (5 5 Vip-). Vip- , , -, , .</li> <li class="e-offer__feature"> Home Club - 3- : <ul> <li> ;</li> <li>;</li> <li> .</li> </ul> </li> <li class="e-offer__feature"> : <ul> <li> 4- 7- ;</li> <li>1 : , -, , ;</li> <li>2 : 2- , , , , ;</li> <li>3 : 2 , .</li> </ul> </li> <li class="e-offer__feature"> VIP-: <ul> <li> 4- 11- ;</li> <li>1 : , 10 , , , , , ;</li> <li>2 : -12 , 2- 4- ( ), 2 ..</li> </ul> </li> <li class="e-offer__feature"> : <a data-seohide-href="/deal/away/20056/" class="e-offer__feature--link seohide-link" target="_blank" rel="nofollow" title="http://www.home-club.kz/">www.home-club.kz/</a> </li> </ul>", "imagesLinks": [ "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/1_20150312023051426147565.7364.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/2_20150312023051426147565.9348.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/4_20150312093171426174997.7985.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/5_20150312093171426174997.944.jpg", "https://www.chocolife.me/" ], "timeToCompletion": null, "mainImageLink": "https://www.chocolife.me/", "originalCouponPrice": "30 000", "originalPrice": "30 000", "discountPercent": "-51%", "discountPrice": "18 000", "discountType": "full", "boughtCount": "1", "sourceServiceCategories": "1 , 82 , 8 , 2", "pageLink": "https://www.chocolife.me//20056-arenda-kottedzha-s-dvumya-spalnyami-gorki-sauna-darts-i-mnogoe-drugoe-v-prirodno-razvlekatelnom-parke-home-club-skidka-do-50", "isArchive": 1, "tryToUpdateCount": 0, "viewCount": "0", "serviceName": "Chocolife.me", "cityName": "" }, { "id": 2, "sourceServiceId": 1, "cityId": 1, "createTimestamp": "2015-03-12 14:01:57", "lastUpdateDateTime": "2016-11-01 12:39:53", "recordHash": "dce10232f1acb53b1ee7a8bf3902e0c0", "title": " AquaBike Centre", "shortDescription": " ", "longDescription": null, "conditions": null, "features": " <p class="e-offer__features">:</p> <ul class="b-offer__features-list"> <li class="e-offer__feature"> . .</li> <li class="e-offer__feature"> Aquabike – : <ul> <li> ;</li> <li> ;</li> <li> ;</li> <li> ;</li> <li> ;</li> <li> ;</li> <li> ;</li> <li> .</li> </ul> </li> <li class="e-offer__feature"> <strong> :</strong> <ul> <li> ;</li> <li> ;</li> <li> , ;</li> <li> ;</li> <li> , ;</li> <li> , ;</li> <li> ;</li> <li> , , .</li> </ul> </li> <li class="e-offer__feature"> <strong> 1 .</strong> </li> <li class="e-offer__feature"> AquaBike Centre : <ul> <li> , 2 ;</li> <li> ;</li> <li>, 45 .</li> </ul> </li> </ul>", "imagesLinks": [ "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20016/660x305/1_20150314013241426318344.7033.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20016/660x305/2_20150314013241426318344.8157.JPG", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20016/660x305/4_20150311053411426073981.6524.JPG", "https://www.chocolife.me/" ], "timeToCompletion": null, "mainImageLink": "https://www.chocolife.me/", "originalCouponPrice": "3 000", "originalPrice": "3 000", "discountPercent": "-50%", "discountPrice": "1 500", "discountType": "full", "boughtCount": "58", "sourceServiceCategories": "1 , 68 , 36 , 2", "pageLink": "https://www.chocolife.me//20016-novinka-iz-francii-vse-dlya-vashey-krasoty-zdorovya-i-relaksacii-trenirovki-po-akvabaykingu-a-takzhe-pressoterapiya-so-skidkoy-50-v-aquabike-centre", "isArchive": 1, "tryToUpdateCount": 0, "viewCount": "0", "serviceName": "Chocolife.me", "cityName": "" }, ...]
/ v1 /クーポン/ {id} { "id": 1, "sourceServiceId": 1, "cityId": 1, "createTimestamp": "2015-03-12 14:01:57", "lastUpdateDateTime": "2016-10-20 03:54:47", "recordHash": "e7b01c1a69bc66e1f1a62d8fcb0825de", "title": "Home Club", "shortDescription": " , , , ", "longDescription": " – . , – - Home Club. 50%! , , . ! ", "conditions": " <p class="e-condition__text">:</p> <ul class="b-conditions-list"> <li class="e-condition"> - Home Club.</li> <li class="e-condition"> <strong></strong>: 50% - 1 500 . 3 000 .</li> <li class="e-condition"> ( ).</li> <li class="e-condition"> .</li> <li class="e-condition"> 10 .</li> <li class="e-condition"> <strong>VIP- . VIP- .</strong> </li> <li class="e-condition"> .</li> <li class="e-condition"> <strong> :</strong><br> +7 (727) 308-23-63,<br> +7 (747) 841-42-51,<br> +7 (701) 985-90-72.</li> <li class="e-condition"> <strong> .</strong> </li> <li class="e-condition"> 2 , ( ).</li> <li class="e-condition"> , «».</li> <li class="e-condition"> Home Club : , , , Home Club.</li> <li class="e-condition"> , , .</li> <li class="e-condition"> <strong> .</strong> </li> <li class="e-condition"> 12 2015 . ().</li> <li class="e-condition"> <span hashstring="deal_refunds_policy" hashtype="content"> </span> </li> <li class="e-condition"> <span hashstring="deal_standard_conditions" hashtype="content"> </span> </li> </ul> <p class="e-offer__features"></p> <ul class="b-offer__features-list"> <li class="e-offer__feature "> , , , Home Club </li> <li class="e-offer__feature "> :<br> +7 (727) 308-23-63<br>+7 (747) 841-42-51<br>+7 (701) 985-90-72<br> </li> <li class="e-offer__feature "> :<br> : </li> </ul>", "features": " <p class="e-offer__features">:</p> <ul class="b-offer__features-list"> <li class="e-offer__feature">Home Club , , .</li> <li class="e-offer__feature"> 10 (5 5 Vip-). Vip- , , -, , .</li> <li class="e-offer__feature"> Home Club - 3- : <ul> <li> ;</li> <li>;</li> <li> .</li> </ul> </li> <li class="e-offer__feature"> : <ul> <li> 4- 7- ;</li> <li>1 : , -, , ;</li> <li>2 : 2- , , , , ;</li> <li>3 : 2 , .</li> </ul> </li> <li class="e-offer__feature"> VIP-: <ul> <li> 4- 11- ;</li> <li>1 : , 10 , , , , , ;</li> <li>2 : -12 , 2- 4- ( ), 2 ..</li> </ul> </li> <li class="e-offer__feature"> : <a data-seohide-href="/deal/away/20056/" class="e-offer__feature--link seohide-link" target="_blank" rel="nofollow" title="http://www.home-club.kz/">www.home-club.kz/</a> </li> </ul>", "imagesLinks": [ "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/1_20150312023051426147565.7364.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/2_20150312023051426147565.9348.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/4_20150312093171426174997.7985.jpg", "https://static.chocolife.me/static/upload/images/deal/for_deal_page/21000/20056/660x305/5_20150312093171426174997.944.jpg", "https://www.chocolife.me/" ], "timeToCompletion": null, "mainImageLink": "https://www.chocolife.me/", "originalCouponPrice": "30 000", "originalPrice": "30 000", "discountPercent": "-51%", "discountPrice": "18 000", "discountType": "full", "boughtCount": "1", "sourceServiceCategories": "1 , 82 , 8 , 2", "pageLink": "https://www.chocolife.me//20056-arenda-kottedzha-s-dvumya-spalnyami-gorki-sauna-darts-i-mnogoe-drugoe-v-prirodno-razvlekatelnom-parke-home-club-skidka-do-50", "isArchive": 1, "tryToUpdateCount": 0, "viewCount": "0", "serviceName": "Chocolife.me", "cityName": "" }
まあ、このような...私はそれを作って、写真付きの部分配列を含む多くの異なる種類のデータが存在するようにしました。私たちはこのすべての善で何をすべきでしょうか?もちろんハンドル!このセクションでは、コメント付きのドライコードがありますが、正しい順序になっています。だから、すべてが明確でなければなりません。そのため、まず、SkidKZApi APIクラスを作成し、サーバーデータを操作するためのメソッドを実装します。skidkzapi.h #ifndef SKIDKZAPI_H #define SKIDKZAPI_H #include "apibase.h" #include <QtQml> class SkidKZApi : public APIBase { Q_OBJECT public: Q_INVOKABLE explicit SkidKZApi(); // . QML static void declareQML() { qmlRegisterType<SkidKZApi>("com.github.qtrestexample.skidkzapi", 1, 0, "SkidKZApi"); } // ReadOnly QNetworkReply *handleRequest(QString path, QStringList sort, Pagination *pagination, QVariantMap filters = QVariantMap(), QStringList fields = QStringList(), QString id = 0); // /v1/coupon QNetworkReply *getCoupons(QStringList sort, Pagination *pagination, QVariantMap filters = QVariantMap(), QStringList fields = QStringList()); // /v1/coupon/{id} QNetworkReply *getCouponDetail(QString id); // /v1/categories QNetworkReply *getCategories(QStringList sort, Pagination *pagination); }; #endif // SKIDKZAPI_H
skidkzapi.cpp #include "skidkzapi.h" #include <QFile> #include <QTextStream> #include <QUrlQuery> SkidKZApi::SkidKZApi() : APIBase(0) { } QNetworkReply *SkidKZApi::handleRequest(QString path, QStringList sort, Pagination *pagination, QVariantMap filters, QStringList fields, QString id) { // , if (path == "/v1/coupon") { return getCoupons(sort, pagination, filters, fields); } else if (path == "/v1/coupon/{id}") { return getCouponDetail(id); } else if (path == "/v1/categories") { return getCategories(sort, pagination); } } // , , QNetworkReply *SkidKZApi::getCoupons(QStringList sort, Pagination *pagination, QVariantMap filters, QStringList fields) { // QUrl url = QUrl(baseUrl()+"/v1/coupon"); QUrlQuery query; // if (!sort.isEmpty()) { query.addQueryItem("sort", sort.join(",")); } // switch(pagination->policy()) { case Pagination::PageNumber: query.addQueryItem("per-page", QString::number(pagination->perPage())); query.addQueryItem("page", QString::number(pagination->currentPage())); break; case Pagination::None: case Pagination::Infinity: case Pagination::LimitOffset: case Pagination::Cursor: default: break; } // . , - if (!filters.isEmpty()) { QMapIterator<QString, QVariant> i(filters); while (i.hasNext()) { i.next(); query.addQueryItem(i.key(), i.value().toString()); } } // if (!fields.isEmpty()) { query.addQueryItem("fields", fields.join(",")); } // url.setQuery(query.query()); // GET QNetworkReply *reply = get(url); return reply; } // QNetworkReply *SkidKZApi::getCouponDetail(QString id) { if (id.isEmpty()) { qDebug() << "ID is empty!"; return 0; } // GET QUrl url = QUrl(baseUrl()+"/v1/coupon/"+id); QNetworkReply *reply = get(url); return reply; } // , QNetworkReply *SkidKZApi::getCategories(QStringList sort, Pagination *pagination) { // QUrl url = QUrl(baseUrl()+"/v1/categories"); QUrlQuery query; // if (!sort.isEmpty()) { query.addQueryItem("sort", sort.join(",")); } // switch(pagination->policy()) { case Pagination::PageNumber: query.addQueryItem("per-page", QString::number(pagination->perPage())); query.addQueryItem("page", QString::number(pagination->currentPage())); break; case Pagination::None: case Pagination::Infinity: case Pagination::LimitOffset: case Pagination::Cursor: default: break; } url.setQuery(query.query()); QNetworkReply *reply = get(url); return reply; }
APIクラスの準備が完了しました。いくつかの簡単なメソッドで、現時点で必要なすべてのサーバー作業を実装しました。次に、モデルを使用するための2つのオプションを検討します。カテゴリには、ライブラリに組み込まれたJsonRestListModelモデルを使用し、クーポンには、AbstractJsonListModelから継承したモデルを使用します。ticketmodel.h #ifndef COUPONMODEL_H #define COUPONMODEL_H #include "abstractjsonrestlistmodel.h" #include "api/skidkzapi.h" class CouponModel : public AbstractJsonRestListModel { Q_OBJECT public: explicit CouponModel(QObject *parent = 0);
bonusmodel.cpp #include "couponmodel.h" CouponModel::CouponModel(QObject *parent) : AbstractJsonRestListModel(parent) { } QNetworkReply *CouponModel::fetchMoreImpl(const QModelIndex &parent) { Q_UNUSED(parent)
できた!
データを受信し、GUIパーツに接続するために必要なものはすべて揃っています。まず、main.cppでdeclareQMLメソッドを呼び出すことを忘れないでください。例はソースにあります。それでは-通常どおり、QMLアプリケーションを作成し、モデルをデータソースとして使用します。somewhere.qml ... import com.github.qtrestexample.skidkzapi 1.0 import com.github.qtrest.jsonrestlistmodel 1.0 import com.github.qtrest.pagination 1.0 import com.github.qtrest.requests 1.0 ...
それだけです。ListViewでモデルを使用する例を紹介するつもりはありません。ソースコードとサンプルアプリケーション
まあ、実際に私たちは最も興味深いものに渡します。プロジェクト全体は、GitHubの次のアドレスにあります。上記のすべてが誰かにとって有用であり、自分のニーズに合わせてAPIクライアントを開発する時間を無駄にしないことを願っています。PS:残念なことに、技術記事に関する議論は3年前からありませんでした。トピックに興味がある場合は、コメントを書くようにしてください。 =)