Мы создадим приложение PHP для запроса API eBay, а затем создадим веб-приложение для получения штрих-кода и отправки его в наш API.
У eBay есть бесплатный API для доступа к своим данным листинга, а у HTML есть API камеры для получения изображений с камеры через веб-страницу. Это означает, что мы можем создавать приложения, которые сканируют штрих-коды из вашего веб-приложения, получают код и отправляют его в API eBay для запроса.
В этой истории мы создадим приложение PHP для запроса API eBay, а затем создадим веб-приложение для получения штрих-кода и отправки его в наш API. Задняя часть проста. Это просто скрипт для получения данных из eBay API через код ISBN. Создайте папку для внутреннего приложения и поместите в нее следующее:
<?php use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use GuzzleHttp\Client; $ebay = $app['controllers_factory']; $ebayAppId = $_ENV['EBAY_APP_ID']; $client = new Client([ 'base_uri' => 'http://svcs.ebay.com' ]); $ebay->get('/find-by-code/{code}/{page}', function ($code, $page) use ($app, $client, $ebayAppId) { if (strlen($code) == 10 || strlen($code) == 13){ $type = 'ISBN'; } else if (strlen($code) == 12){ $type = 'UPC'; } else{ return $app->json(['error' => 'invalid code'], 400); } if (!is_int(intval($page)) || $page <= 0){ return $app->json(['error' => 'invalid page'], 400); } $response = $client->request('GET', "/services/search/FindingService/v1?OPERATION-NAME=findItemsByProduct&SERVICE-VERSION=1.0.0&SECURITY-APPNAME=$ebayAppId&RESPONSE-DATA-FORMAT=JSON&REST-PAYLOAD&paginationInput.entriesPerPage=10&productId.@type=$type&productId=$code&paginationInput.pageNumber=$page"); return $app->json(json_decode($response->getBody(), true)); }); return $ebay;
Мы называем это ebay.php
. Часть function ($code, $page) use ($app, $client, $ebayAppId)
позволяет маршруту получить доступ к внешним переменным, поскольку обратный вызов находится в другой области, чем внешние переменные.
Затем в index.php
положим
<?php require_once 'vendor/autoload.php'; $app = new Silex\Application(); $dotenv = new Dotenv\Dotenv('.'); $dotenv->load(); $app->register(new JDesrosiers\Silex\Provider\CorsServiceProvider(), [ "cors.allowOrigin" => "*", ]); $app['debug']= true; $app->get('/hello/{name}', function($name) use($app) { return 'Hello '.$app->escape($name); }); $app->mount('/ebay', include 'ebay.php'); $app["cors-enabled"]($app); $app->run();
чтобы мы могли получить доступ к нашим маршрутам.
В composer.json
положим
{ "require": { "silex/silex": "^2.0", "vlucas/phpdotenv": "^2.4", "jdesrosiers/silex-cors-provider": "~1.0", "guzzlehttp/guzzle": "~6.0" } }
поэтому мы можем запустить composer install
для установки наших зависимостей, если установлен Composer.
Теперь, когда у нас есть бэкэнд. Мы можем сделать интерфейс. Приложение будет построено на Angular. Мы строим приложение с помощью Angular CLI. Мы запускаем ng new frontend
, чтобы построить приложение.
Единственное, что отличается от наших типичных приложений, это то, что для доступа к камере требуется HTTPS, поэтому мы должны создать собственный сертификат HTTPS для нашего сервера разработки. У нас должны быть файлыserver.crt
и server.key
в той же папке, что и файлы интерфейсного приложения.
В разделе serve
нашего angular.json
должны быть:
"serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "frontend:build", "sslKey": "server.key", "sslCert": "server.cert" }, "configurations": { "production": { "browserTarget": "frontend:build:production" }, "mobile": { "browserTarget": "frontend:build:mobile" } } },
куда
"sslKey": "server.key", "sslCert": "server.cert"
должен ссылаться на путь ваших сертификатов.
Затем, чтобы запустить сервер разработки Angular CLI, мы запускаем:
ng serve --ssl
Когда вы перейдете на https: // localhost: 4200, вы увидите ошибку небезопасного соединения в большинстве браузеров. Нажмите Продолжить, чтобы продолжить.
Если ваша камера установлена на вашем Android-устройстве, мы можем выполнить отладку удаленно. В Chrome нажмите F12, чтобы открыть консоль, щелкните в правом верхнем углу меню с тремя вертикальными точками. Затем нажмите Удаленные устройства, подключите Android-устройство к компьютеру и включите удаленную отладку согласно инструкциям.
Вместо запуска ng serve --ssl
вы запускаете ng serve --ssl --host 0.0.0.0
Как только все это будет сделано, вы должны увидеть следующее:
как только приложение будет построено.
Мы устанавливаем библиотеку для доступа к камере устройства и нашему хранилищу потоков, запустив:
npm i @zxing/ngx-scanner @ngrx/store @angular/material @angular/cdk
В app.module.ts
мы помещаем:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { ZXingScannerModule } from '@zxing/ngx-scanner'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { MatTabsModule } from '@angular/material/tabs'; import { MatCardModule } from '@angular/material/card'; import { HttpClientModule } from '@angular/common/http'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; import { MatSelectModule } from '@angular/material/select'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { HomePageComponent } from './home-page/home-page.component'; import { StoreModule } from '@ngrx/store'; import { barCodeReducer } from './bar-code-reducer'; import { FormsModule } from '@angular/forms'; import { EbayTabComponent } from './ebay-tab/ebay-tab.component'; @NgModule({ declarations: [ AppComponent, HomePageComponent, EbayTabComponent ], imports: [ BrowserModule, AppRoutingModule, ZXingScannerModule, BrowserAnimationsModule, MatButtonModule, MatToolbarModule, MatInputModule, MatTabsModule, StoreModule.forRoot({ barCode: barCodeReducer }), FormsModule, MatCardModule, HttpClientModule, MatPaginatorModule, MatTableModule, MatSelectModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Сюда входят компоненты Angular Material и наш магазин flux.
Теперь нам нужно создать централизованное хранилище для наших данных. Мы создаем файл с именем bar-code-reducer.ts
и добавляем следующее:
export const SET_BARCODE = 'SET_BARCODE'; export function barCodeReducer(state: string = '', action) { switch (action.type) { case SET_BARCODE: return action.payload; default: return state; } }
Теперь мы можем добавить наши внешние компоненты. Мы бежим:
ng g component ebayTab ng g component homePage
Это добавляет страницу для отображения нашего сканера штрих-кода и раздел для отображения наших данных eBay.
Затем мы создаем службу для создания нашего HTTP-запроса, запустив:
ng g service productSearch
После этого у нас должно получиться produce-search.service.ts
. Мы помещаем туда следующее:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root' }) export class ProductSearchService { constructor( private http: HttpClient ) { } searchProduct(barcode: string, currentPage: number) { return this.http.get(`${environment.apiUrl}/ebay/find-by-code/${barcode}/${currentPage}`) } }
В ebay-tab.component.ts
мы помещаем:
import { Component, OnInit } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { ProductSearchService } from '../product-search.service'; import { ConstantPool } from '@angular/compiler'; @Component({ selector: 'app-ebay-tab', templateUrl: './ebay-tab.component.html', styleUrls: ['./ebay-tab.component.css'] }) export class EbayTabComponent implements OnInit { barcode$: Observable<string>; barcodeValue: string; products: any[] = []; totalPages: number = 0; totalEntries: number = 0; entriesPerPage: number = 0; currentPage: number = 1; displayedColumns: string[] = [ 'itemId', 'title', 'location', 'country', 'shippingServiceCost', 'currentPrice', 'convertedCurrentPrice', 'bestOfferEnabled', 'buyItNowAvailable', 'listingType' ]; constructor( private store: Store<any>, private productSearchService: ProductSearchService ) { this.barcode$ = store.pipe(select('barCode')) this.barcode$.subscribe(barcode => { this.barcodeValue = barcode; this.products = []; this.searchProduct(this.barcodeValue, this.currentPage); }, err => { }) } ngOnInit() { } searchProduct(barcodeValue, currentPage) { this.productSearchService.searchProduct(barcodeValue, currentPage) .subscribe((res: any) => { try { this.products = res.findItemsByProductResponse[0].searchResult[0].item as any[]; this.products = this.products.map(p => { let shippingServiceCost = p.shippingInfo[0].shippingServiceCost; let sellingStatus = p.sellingStatus; return { itemId: p.itemId, title: p.title, country: p.country, location: p.location, shippingServiceCost: Array.isArray(shippingServiceCost) ? `${shippingServiceCost[0]['__value__']} ${shippingServiceCost[0]['@currencyId']}` : '', currentPrice: Array.isArray(sellingStatus) ? `${sellingStatus[0].currentPrice[0]['__value__']} ${sellingStatus[0].currentPrice[0]['@currencyId']}` : '', convertedCurrentPrice: Array.isArray(sellingStatus) ? `${sellingStatus[0].convertedCurrentPrice[0]['__value__']} ${sellingStatus[0].convertedCurrentPrice[0]['@currencyId']}` : '', bestOfferEnabled: p.listingInfo[0].bestOfferEnabled[0], buyItNowAvailable: p.listingInfo[0].buyItNowAvailable[0], listingType: p.listingInfo[0].listingType[0] } }) this.totalPages = res.findItemsByProductResponse[0].paginationOutput[0].totalPages[0]; this.totalEntries = res.findItemsByProductResponse[0].paginationOutput[0].totalEntries[0]; this.entriesPerPage = res.findItemsByProductResponse[0].paginationOutput[0].entriesPerPage[0]; } catch (ex) { this.products = []; } }, err => { this.products = []; }) } getProducts(event) { this.currentPage = event.pageIndex + 1; this.searchProduct(this.barcodeValue, this.currentPage); } }
А в ebay-tab.component.html
у нас есть:
<div *ngIf='products.length > 0'> <table mat-table [dataSource]="products" class="mat-elevation-z8"> <ng-container matColumnDef="itemId"> <th mat-header-cell *matHeaderCellDef> Item ID </th> <td mat-cell *matCellDef="let element"> {{element.itemId}} </td> </ng-container> <ng-container matColumnDef="title"> <th mat-header-cell *matHeaderCellDef> Title </th> <td mat-cell *matCellDef="let element"> {{element.title}} </td> </ng-container> <ng-container matColumnDef="location"> <th mat-header-cell *matHeaderCellDef> Location </th> <td mat-cell *matCellDef="let element"> {{element.location}} </td> </ng-container> <ng-container matColumnDef="country"> <th mat-header-cell *matHeaderCellDef> Country </th> <td mat-cell *matCellDef="let element"> {{element.country}} </td> </ng-container> <ng-container matColumnDef="shippingServiceCost"> <th mat-header-cell *matHeaderCellDef> Shipping Cost </th> <td mat-cell *matCellDef="let element"> {{element.shippingServiceCost}} </td> </ng-container> <ng-container matColumnDef="currentPrice"> <th mat-header-cell *matHeaderCellDef> Current Price </th> <td mat-cell *matCellDef="let element"> {{element.currentPrice}} </td> </ng-container> <ng-container matColumnDef="convertedCurrentPrice"> <th mat-header-cell *matHeaderCellDef> Converted Current Price </th> <td mat-cell *matCellDef="let element"> {{element.convertedCurrentPrice}} </td> </ng-container> <ng-container matColumnDef="bestOfferEnabled"> <th mat-header-cell *matHeaderCellDef> Best Offer Enabled </th> <td mat-cell *matCellDef="let element"> {{element.bestOfferEnabled}} </td> </ng-container> <ng-container matColumnDef="buyItNowAvailable"> <th mat-header-cell *matHeaderCellDef> Buy It Now </th> <td mat-cell *matCellDef="let element"> {{element.buyItNowAvailable}} </td> </ng-container> <ng-container matColumnDef="listingType"> <th mat-header-cell *matHeaderCellDef> Listing Type </th> <td mat-cell *matCellDef="let element"> {{element.listingType}} </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table> <mat-paginator [length]="totalEntries" [pageSize]="entriesPerPage" (page)='getProducts($event)'> </mat-paginator> </div> <div *ngIf='products.length == 0' class="center"> <h1>No Results</h1> </div>
В home-page.component.ts
мы помещаем:
import { Component, OnInit, ViewChild } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { SET_BARCODE } from '../bar-code-reducer'; import { NgForm } from '@angular/forms'; import { BarcodeFormat } from '@zxing/library'; import { ZXingScannerComponent } from '@zxing/ngx-scanner'; @Component({ selector: 'app-home-page', templateUrl: './home-page.component.html', styleUrls: ['./home-page.component.css'] }) export class HomePageComponent implements OnInit { barcodeValue: number; webCamAvailable: boolean = true; barcode$: Observable<string>; searching: boolean = false; allowedFormats = [ BarcodeFormat.QR_CODE, BarcodeFormat.EAN_13, BarcodeFormat.CODE_128, BarcodeFormat.DATA_MATRIX, BarcodeFormat.UPC_A, BarcodeFormat.UPC_E, BarcodeFormat.UPC_EAN_EXTENSION, BarcodeFormat.CODABAR, BarcodeFormat.CODE_39, BarcodeFormat.CODE_93 ]; hasCameras = false; hasPermission: boolean; qrResultString: string; availableDevices: MediaDeviceInfo[]; selectedDevice: MediaDeviceInfo; @ViewChild('scanner') scanner: ZXingScannerComponent; constructor( private store: Store<any> ) { } ngOnInit() { this.scanner.camerasFound.subscribe((devices: MediaDeviceInfo[]) => { this.hasCameras = true; this.availableDevices = devices; }); this.scanner.permissionResponse.subscribe((answer: boolean) => { this.hasPermission = answer; }); } onValueChanges(result) { this.barcodeValue = result.codeResult.code; this.searching = true; this.store.dispatch({ type: SET_BARCODE, payload: this.barcodeValue }); } searchProduct(barCodeForm: NgForm) { this.searching = false; if (barCodeForm.invalid) { return; } this.searching = true; this.store.dispatch({ type: SET_BARCODE, payload: this.barcodeValue }); } scanSuccessHandler(event) { console.log(event); this.barcodeValue = event; this.store.dispatch({ type: SET_BARCODE, payload: this.barcodeValue }); } onDeviceSelectChange(selectedValue: string) { this.selectedDevice = this.scanner.getDeviceById(selectedValue); } scanErrorHandler(event) { console.log(event); } scanFailureHandler(event) { console.log(event); } scanCompleteHandler(event) { console.log(event); this.barcodeValue = event.text; this.store.dispatch({ type: SET_BARCODE, payload: this.barcodeValue }); } }
Помимо сканирования, вы также можете ввести штрих-код вручную. Если камера присутствует, вы должны увидеть окно предварительного просмотра. После сканирования мы получаем значение штрих-кода и передаем его компоненту ebay-tab
, который мы создали через хранилище потока.