• Используйте программу блокчейн multichain для соединения двух капель Digital Ocean.
  • Установка Drupal 8 на одну ноду
  • Приложение Angular 5 на другом узле
  • Обновления Drupal будут распространяться в реальном времени на Angular через блокчейн.
  • Это часть 2, где я загружаю выходные данные блокчейна в Angular.
  • Часть 1: https://medium.com/@blockbinder/blockchain-in-action-a-working-example-part-1-writing-drupal-output-to-blockchain-efb2ff1df73
  • Это вторая часть рабочей демонстрации интеграции веб-приложений с блокчейном. Эта демонстрация является примером содержимого Drupal, записываемого в блокчейн и отображаемого в реальном времени в приложении Angular 5. В этой части мы будем использовать веб-сокеты, Angular и multichain для отображения потока данных, поступающих из блокчейна, в реальном времени.

Демо:

  1. Показатель:
  2. Введение
  3. Шаг 1. Напишите в Steam
  4. Шаг 2: подготовьте Angular
  5. Вывод

Chainfrog действительно счастливы и взволнованы тем интересом, который мы проявляем к нашей деятельности в области блокчейнов в последнее время. После разработки запатентованного инструмента синхронизации базы данных для безопасного обмена данными через блокчейн, мы получили самый убедительный отклик - это желание увидеть под капотом. Поскольку я не могу передать наш IP-адрес, я удалил наш инструмент из середины и получил полную демонстрацию использования блокчейна в качестве протокола веб-коммуникации. Если вы хотите немного узнать о том, как и почему, не стесняйтесь вернуться к моим предыдущим блогам:







Если вы хотите заполучить сочный код, читайте дальше. Это руководство позволит вам начать работу с инструментами с открытым исходным кодом за несколько часов.

Шаг 1. Подключите демон multichain и напишите в Steam

На предыдущем шаге мы настроили два наших облачных узла и подключили их к одной и той же цепочке блоков. Теперь нам нужен способ подключить вывод нашей цепочки блоков к нашему веб-приложению. Есть много способов сделать это (например, мы могли бы использовать drush для обновления Drupal, или мы могли бы использовать обратный прокси и отправлять данные через http-запрос), но в этой демонстрации я решил использовать веб-сокеты и каналы. Причина этого в том, что мы можем подписать наше приложение Angular на веб-сокет, а затем получать обновление «в реальном времени» каждый раз, когда блок публикуется в цепочке блоков. Есть много способов снять шкуру с кошки, и я выбрала более интересный, а не обычный!

Итак, первый шаг для нас - подключить демон. В параметрах выполнения нескольких цепочек https://www.multichain.com/developers/runtime-parameters/ мы можем увидеть команду blocknotify, команду, выполняемую каждый раз, когда новый блок публикуется в цепочке блоков.

blocknotify Выполните эту команду, когда новый блок добавляется в конец текущей цепочки. %s в параметрах команды будет заменен хешем блока.

Итак, план состоит в том, чтобы просто записать хэш блока в FIFO в Linux, а затем подписаться на этот файл через наш обратный прокси-сервер ExpressJS.

# login to node 2
cd /var/www/medium-demo-angular
mkdir server
cd server
mkfifo blocks.fifo
# Write block hashes to fifo when initalize daemon
multichaind [email protected]:7891 -blocknotify="echo '%s'  > /var/www/medium-demo-angular/server/blocks.fifo" -daemon
#Check this is working
nano fifo.sh

fifo.sh

#!/bin/bash
pipe=/var/www/medium-demo-angular/server/blocks.fifo
trap "rm -f $pipe" EXIT
while true
do
    if read line <$pipe; then
        echo $line
    fi
done
echo "Reader exiting"
#exit

Теперь мы можем запустить скрипт!

sh fifo.sh

Теперь, если мы просто добавим контент на https://medium-demo-drupal.blockbinder.com (или на ваш узел 1), терминал будет подписан на FIFO, и вы должны получить результат, как показано ниже. Хеши из нашего блокчейна!

cd /var/www/medium-demo-angular
mkdir server
cd server
# Setup express server
nano package.json

package.json

{
  "name": "auth",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {},
  "dependencies": {
    "body-parser": "^1.18.2",
    "express": "^4.16.2",
    "ws": "^5.0.0"
  }
}

Установить пакеты

npm install
node server.js

Настроить экспресс-сервер

const express = require('express'), bodyParser = require('body-parser');
const http = require('http');
const url = require('url');
const WebSocket = require('ws');
const app = express();
app.use(bodyParser.json())
var exec = require('child_process').exec;
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
var fs = require('fs')
wss.broadcast = function broadcast(msg) {
   console.log(msg);
   wss.clients.forEach(function each(client) {
       client.send(msg);
    });
};
wss.on('connection', function connection(ws, req) {
        const location = url.parse(req.url, true);
        // You might use location.query.access_token to authenticate or share sessions
        // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
        console.log('Server is connected');
// PIPE LISTENER
        const fd = fs.openSync('/var/www/medium-demo-angular/server/blocks.fifo', 'r+')
        const stream = fs.createReadStream(null, {fd})
        stream.on('data', data => {
        function puts(error, stdout, stderr) { sys.puts(stdout) }
        exec("multichain-cli medium-demo-blockchain liststreamitems root", function(error, stdout, stderr) {
          if (!error) {
// things worked!
            // console.log(stdout)
            wss.broadcast(stdout);
} else {
                console.log(stderr)
            // things failed :(
       }
        });
})
ws.on('close', function(code, reason) {
            console.log(code);
            console.log(reason);
        });
});
app.get("/retrieve-database" , function (request, response) {
function puts(error, stdout, stderr) { sys.puts(stdout) }
        exec("multichain-cli medium-demo-blockchain liststreamitems root", function(error, stdout, stderr) {
          if (!error) {
// things worked!
            console.log(stdout)
                response.send(stdout)
} else {
                console.log(error)
            // things failed :(
}
        });
});
server.listen(3500, function listening() {
  console.log('Listening on %d', server.address().port);
});

Поздравляем, теперь у нас есть обратный прокси-сервер ExpressJS, открывающий веб-сокет, который подписан на канал! Круто, не правда ли (не самый эффективный метод, но определенно самый увлекательный). Теперь нам нужно подписать Angular на веб-сокет, чтобы обеспечить обновление HTML в реальном времени.

Шаг 2 - Подключите Angular к экспресс-серверу.

Теперь нам просто нужен способ подключения Angular к экспресс-серверу. Есть несколько файлов, которые вам нужно будет отредактировать на стороне Angular:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
// Material  inputs
import { MatInputModule, MatCheckboxModule, MatToolbarModule } from '@angular/material';
import { MatIconModule } from '@angular/material';
import { MatListModule } from '@angular/material';
import { MatCardModule } from '@angular/material';
import { MatButtonModule } from '@angular/material';
import { MatGridListModule } from '@angular/material';
import { MatMenuModule } from '@angular/material';
import { MatSidenavModule } from '@angular/material';
import { MatChipsModule } from '@angular/material';
import { MatRadioModule, MatPaginatorModule } from '@angular/material';
import { MatStepperModule } from '@angular/material/stepper';
import { MatSelectModule } from '@angular/material/select';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTableModule } from '@angular/material/table';
import { DataService } from './data.service';
import { WebSocketService } from './websocket.service';
import { AppComponent } from './app.component';
import { HeaderComponent } from './blocks/header/header.component';
import { FooterComponent } from './blocks/footer/footer.component';
import { FlexLayoutModule } from '@angular/flex-layout';
@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    FooterComponent
  ],
  imports: [
    BrowserModule,
    FormsModule, 
    ReactiveFormsModule,
    MatIconModule,
    MatListModule,
    MatCardModule,
    MatInputModule, 
    MatCheckboxModule,
    MatButtonModule,
    MatGridListModule,
    MatChipsModule,
    MatMenuModule,
    MatSidenavModule,
    MatSelectModule,
    MatRadioModule,
    MatStepperModule,
    MatPaginatorModule,
    MatToolbarModule,
    MatTabsModule,
    MatTableModule,
    HttpModule,
    HttpClientModule,
    BrowserAnimationsModule,
    FlexLayoutModule
  ],
  providers: [DataService, WebSocketService],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component, ViewChild, ElementRef, OnInit, AfterViewInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { WebSocketSubject } from 'rxjs/observable/dom/WebSocketSubject';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; // <-- do not forget to import
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatPaginator, MatTableDataSource } from '@angular/material';
import { DataService } from './data.service';
import { WebSocketService } from './websocket.service';
import { trigger,style,transition,animate,keyframes,query,stagger,state } from '@angular/animations';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [DataService, WebSocketService],
  animations: [ trigger('onOffTrigger', [
    state('off', style({
      backgroundColor: '#E5E7E9',
      transform: 'scale(1)'
    })),
    state('on',   style({
      backgroundColor: 'green',
      transform: 'scale(1.1)'
    })),
    transition('off => on', animate('.6s 100ms ease-in')),
    transition('on => off', animate('.7s 100ms ease-out'))
    ])]
  })
export class AppComponent {
  title = 'app';
  addProduct: FormGroup; 
  @ViewChild(MatPaginator) paginator: MatPaginator;
// Data for table
  displayedColumns = ['title','body','date'];
  dataSource = new MatTableDataSource();
array1: any = [];
  array2: any = [];
constructor(
    private _formBuilder: FormBuilder, 
    public dataService : DataService,
    private wsService: WebSocketService
  ) { 
    this.wsService.createObservableSocket('wss://angular.blockbinder.com/api/')
    .subscribe(message =>  {
      console.log(message);
        this.dataService.tableData().subscribe(updatedData => {
          this.array2 = updatedData['data'];
          var length = this.array1.length;
          var length2 = this.array2.length;
          var lengthdifference = length2 - length;
          for(let key in this.array2){
            this.array2[key].active = 'off';
          }
          this.dataSource = new MatTableDataSource(this.array2);
          if(lengthdifference > 0){
            this.array2[0].active = 'on';
          }
          setTimeout(()=>{    //<<<---    using ()=> syntax
            this.array2[0].active = 'off';
          },3000);
        })
    },
    err => console.log(err),
    () => console.log('stream complete')
    );
}
ngAfterViewInit() {
   this.dataService.tableData().subscribe(response => {
      this.array1 = response['data'];
      for(let key in this.array1){
        this.array1[key].active = 'off';
      }
    this.dataSource = new MatTableDataSource(this.array1);
   })
}
}

app.component.html

<app-header></app-header>  
  <mat-sidenav-container>
    <mat-sidenav #sidenav mode="side" class="sidebar" [fixedInViewport]="false" [fixedTopGap]="0" [fixedBottomGap]="0"> 
      <div class="sidebar__container fullwidth">
      </div>
    </mat-sidenav>
    <mat-sidenav-content>
  <div  class="container-fluid primary">
      <div class="container__inner" id="top">
      <section class="main">
      <h1>Welcome to our Medium demo, here you can see Drupal data appearing in real time in an Angular table! The fun bit -- it was transferred via Blockchain</h1>
      <div
      fxLayout
      fxLayout.xs="column"
      fxLayoutAlign="center"
      fxLayoutGap="30px"
      fxLayoutGap.xs="0"
      >
      <div fxFlex>
      <mat-table fxFlex #table [dataSource]="dataSource">
<!-- Value Column -->
        <ng-container matColumnDef="title">
          <mat-header-cell *matHeaderCellDef>Title</mat-header-cell>
<mat-cell *matCellDef="let element">
           {{element.data.json.title}}
         </mat-cell>
</ng-container>
<!-- Value Column -->
        <ng-container matColumnDef="body">
          <mat-header-cell *matHeaderCellDef>body</mat-header-cell>
          <mat-cell *matCellDef="let element">
           {{element.data.json.body}}
         </mat-cell>
</ng-container>
<!-- Value Column -->
        <ng-container matColumnDef="date">
          <mat-header-cell *matHeaderCellDef>date</mat-header-cell>
<mat-cell *matCellDef="let element">
           {{element.blocktime}}
         </mat-cell>
</ng-container>
        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row [@onOffTrigger]="row.active" *matRowDef="let row; columns: displayedColumns;"></mat-row>
      </mat-table>
      </div>
  </div>
</section>
</div>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
<app-footer></app-footer>

package.json

{
  "name": "blockbinder-shop-api",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.2.0",
    "@angular/cdk": "^5.1.0",
    "@angular/common": "^5.2.0",
    "@angular/compiler": "^5.2.0",
    "@angular/core": "^5.2.0",
    "@angular/forms": "^5.2.0",
    "@angular/http": "^5.2.0",
    "@angular/material": "^5.1.0",
    "@angular/platform-browser": "^5.2.2",
    "@angular/platform-browser-dynamic": "^5.2.2",
    "@angular/router": "^5.2.0",
    "@angular/flex-layout": "git+https://github.com/angular/flex-layout-builds.git",
    "core-js": "^2.4.1",
    "rxjs": "^5.5.6",
    "zone.js": "^0.8.19"
  },
  "devDependencies": {
    "@angular/cli": "1.6.6",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "~2.5.3"
  }
}

websocket.service.ts

import { Observable } from 'rxjs/Rx';
export class WebSocketService {
  ws: WebSocket;
  createObservableSocket(url:string): Observable<string>{
   this.ws = new WebSocket(url);
   return new Observable(observer => {
    this.ws.onmessage = (event) => observer.next(event.data);
    this.ws.onerror = (event) => observer.error(event);
    this.ws.onclose = (event) => observer.complete();
   });
  }
}

data.service.ts

import { Injectable, Inject }    from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import 'rxjs/add/operator/toPromise';
class promise {};
@Injectable() 
export class DataService {
host: string = 'https://angular.blockbinder.com/api/';
constructor(private http: HttpClient) {}
tableData() : Observable<promise> {
    return this.http.get(
      this.host+'retrieve-database'
    );
  }
}

Как и в случае с Angular, часто зависимости обновляются, поэтому вам нужно будет внести несколько изменений в свое приложение, чтобы обеспечить безопасную сборку / запуск. Если вам нужен полный код, включая стили и т. Д., Для справки, я поместил его в github здесь: https://github.com/Ejb503/medium-demo-angular

Теперь у нас есть настройка Angular и подписка на блоки, которые выходят из цепочки блоков, давайте создадим приложение.

# load node 2
cd /var/www/medium-demo-angular
ng build

Вывод

В этой демонстрации из двух частей мы настроили два веб-приложения, которые обмениваются данными через блокчейн. У нас есть приложение Drupal на одном конце, и по мере добавления контента оно отображается в Angular 5 на другом конце. Хотя это был пример a- ›b через блокчейн, мы также можем расширить его для более полезных приложений. a- ›b, c, d, например, когда несколько узлов обновляют свои данные при обновлении источника. Это также может быть двусторонним способом, поэтому изменения из приложения Angular возвращаются в Drupal.

Это всего лишь простой прототип и пример использования блокчейна для синхронизации веб-приложений! Пожалуйста, прочтите другие блоги, чтобы узнать больше о дебатах о том, «почему» вы выбрали именно это!

Ответ на COVID-19 Blockchain - ›

В эти трудные времена мы опубликовали систему POC для распределенной системы блокчейн, чтобы обеспечить потоки данных и реализовать систему светофора, следите за блогами здесь: https://medium.com/@ed_burton/a-blockchain-covid- система ответов poc-889d9b74786e