Пояснительная записка к электронному журналу с использованием технологии Блокчейн, практическая часть: часть 1 и 2
2.1 Установка серверной части
2.1.1 Создание сервера:
Для этого захожу на https://vscale.io/panel и авторизовавшись, нажимаю
«Создать сервер».
Далее выбираю второй вариант за
400 рублей в месяц и нажимаю «создать сервер». Появляется окно с данными
и кнопкой «ок».
Копируем данные и идём в SSH клиент для дальнейшей работы с ним (Про SSH
клиент putty было написано в 1.2.1).
2.1.2 Работа на сервере:
После запуска putty.exe в папке WinSCP/putty мы видим стандартное окно
подключения, где вводим
root@ip:pass
и нажимаем
open.
Правда после этого придётся ввести пароль ещё раз, но это ничего. Теперь
приступаем к настройке.
Для начала загрузил список пакетов, которые можно будет установить: sudo
apt-get update. После завершения выполняем установку Apache командой
sudo apt-get install apache2 и подтверждаем её, нажав y с Enter:
Когда и она завершилась, переходим к следующему шагу: включению
mod-rewrite и установке mysql. Это делается двумя командами:
sudo a2enmod rewrite
sudo apt-get install mysql-server.
Покажу скрин, но не факт, что в нём будет обе операции, но иначе их
будет слишком много. Далее
добавляем репозиторий php7 командой sudo apt-add-repository
ppa:ondrej/php
Но она у меня не сработала, т. к. не был установлен 1 пакет, который
позволяет добавлять репозитории. Ставим его командой $sudo apt
install software-properties-common
Повторное выполнение добавления прошло успешно.
Далее обновляем список пакетов командой sudo apt-get update и
устанавливаем php7 так:
sudo apt-get install php7.0-cli php7.0-common libapache2-mod-php7.0
php7.0 php7.0-mysql php7.0-fpm php7.0-mbstring php7.0-gd php7.0-curl
2.1.3 Конфигурирование сервера и php
1. Веб-сервер. Для этого переходим в папку apache (cd /etc/apache2) и
редактируем файл (sudo nano apache2.conf):
В конец вставляем
ServerName localhost ServerSignature Off
Задавая хост по умолчанию для самоопределения apache, и отключая
отображения версий сервера и системы на страницах ошибок.
2. php (cd /etc/php/7.0/apache2). Файл php.ini (sudo nano php.ini):
Здесь включаем short_open_tag, заменив off на on, а также установив
memory_limit на 128 и post_max_size на 64 МБ.
После всех действий сохраняем и устанавливаем PhpMyAdmin (Позволит
работать с базой данных). Вряд ли пригодится, но лишней не будет.
Для этого переходим в папку /usr/share (cd /usr/share), скачиваем sudo
wget
https://files.phpmyadmin.net/phpMyAdmin/4.8.5/phpMyAdmin-4.8.5-all-languages.zip,
ставим архиватор (sudo apt-get install unzip) и распаковываем скаченное
ранее: sudo unzip phpMyAdmin-4.8.5-all-languages.zip. Далее
переименовываем папку командой sudo mv
phpMyAdmin-4.8.5-all-languages phpmyadmin, устанавливаем права (sudo
chmod -R 0755 phpmyadmin), и редактируем конфиг apache (sudo nano
/etc/apache2/sites-available/000-default.conf), прописав после
«DocumentRoot /var/www/html»:
Alias /phpmyadmin "/usr/share/phpmyadmin/"
<Directory "/usr/share/phpmyadmin/">
Order allow,deny
Allow from all
Require all granted
</Directory>
Перезапускаем Apache (sudo service apache2 restart) и всё.
Дефолтная страница находится по адресу http://95.213.237.225.
И последним шагом устанавливаем
vsftpd, чтобы можно было работать на сервере через ftp: sudo apt-get
install vsftpd. После чего изменяем файл sudo nano /etc/vsftpd.conf. Тут
надо проверить значения параметров, чтоб они совпадали с описанными
ниже, а также чтоб все они не были закомментированы (Чтобы не было
“#”):
anonymous_enable=No
local_enable=YES
write_enable=YES
После редактирования файла перезапускаем vsftpd командой sudo service
vsftpd restart.
По сути всё, но я ещё создал второго пользователя, т. к. забыл
скопировать пароль от root.
2.1.4 Установка node.js, npm и pm2
Это всё нужно для работы фоновых скриптов, которые будут получать данные
из блокчейна и добавлять их в базу данных.
Для начала устанавливаем Git командой sudo apt-get install git, а после
— выполняем sudo apt-get install -y nodejs и sudo apt-get install npm.
Проверяем их версию командой nodejs -v. У меня v8.10.0 – это старая
версия: необходимо обновить.
Чтобы сделать это вводим команды:
sudo npm cache clean -f
sudo npm install -g n
sudo n stable
Всё. Теперь перезапускаем сервер в панели управления vscale и проверяем
результат, введя node -v 11.8.0.
Далее проверяем версию npm командой npm -v: 6.5.0. Обновляемся на всякий
случай: sudo npm install npm -g. Повторный вызов npm -v показал, что
версия 6.7.0.
И последним шагом ставим pm2: sudo npm install pm2 -g.
pm2 – это модуль, который позволяет запускать скрипты в виде процессов,
т. е. Без необходимости держать открытым окно консоли.
2.1.5 Запуск системы
На этом общая часть закончилась, а начинается часть, связанная с
проектом электронного журнала.
Сначала хочу напомнить, что в проекте используется Nodejs скрипт,
просматривающий блоки и добавляющий данные в mysql, если их
содержимое соответствует нужному.
Здесь я лишь покажу начальную часть: создание папки, загрузку скрипта
через ftp и его запуск через node, а потом — через pm2.
Поскольку у журнала будет веб часть, я решил разместить всё, что для
него нужно, в одной папке: varwww/html/journal. Для этого перехожу
в varwww/html командой cd и создаю journal командой mkdir. После чего
перехожу в неё (cd journal).
Создаю файл package.json командой sudo nano package.json с содержимым
{}, чтобы у nodejs было куда добавлять пакеты.
И после этого устанавливаю нужные nodejs модули (пакеты):
sudo npm install nedb
sudo npm install viz-world-js
sudo npm install mysql
Результат:
Файл package.json имеет содержимое:
{
"dependencies": {
"mysql": "^2.16.0",
"nedb": "^1.8.0",
"viz-world-js": "^0.9.16"
}
}
Закидываю файл со скриптом. Вот каков вид папки:
После этого есть 3 варианта запуска скрипта:
node getData – запуск в стандартном варианте (терминал нельзя
закрывать). Выводит всё на экран (И ошибки, и результаты).
Node getData 1> errors.log – выводит на экран ошибки, а в лог —
результат. Особенно удобен мне, т. к. на экране не всегда всё
можно хорошо услышать;
node getData 2> errors.log – запуск с логами ошибок в файле и
результатом на экране;
pm2 start getData.js – запуск скрипта, как процесса (в отличие от
предыдущих вариантов позволяет закрывать окно терминала).
Я не буду скриншотить всё, т. к. иначе будет слишком много скриншотов,
поэтому покажу лишь 2 из них: node getData 2> errors.log и pm2
getData.js
1. Вариант с ошибками в лог файле и результатом в консоли:
Тут я показал только результат, т. к. в errors.log (файл ошибок), что
появился в папке со скриптом) ничего нет.
2. Вариант с pm2.
Тут отображается после ввода команды лишь список процессов. Поскольку
getData.js единственный, только он:
2.2 Описание процесса разработки
Проект состоит из трёх частей:
node.js часть: получение данных из блокчейна и добавление их в базу
данных;Php: просмотр информации, её изменение и удаление.
Локальная часть: страницы добавления данных в БЧ.
2.2.1 Конфигурационный файл: config.json
Перед тем, как переходить к разделам, скажу, что есть файл конфигов,
который используется и в node.js части, и в php.
Вот его содержимое по умолчанию:
{
"metodist": "denis-skripnik",
"db_server": "localhost",
"db_login": "root",
"db_password": "",
"db_name": "journal"
}
metodist имеет возможности админа (может добавлять всё, просматривать,
изменять и удалять тоже).
2.2.2 Node.js часть
Первый файл — это blockchainData.js. В нём находятся функции получения
данных и добавления их в БД.
Второй — database.js: функции работы с Mysql (позволяют не вводить кучу
раз методы NPM пакета Mysql, а использовать конкретные функции из этого
модуля);
blockchainData.js
В предпоследней строке вызывается runActions (функция без параметров):
отвечает за создание таблиц,
Последняя строка содержит
setInterval(() 😕> run(), 3000);
Вызывается функция run, которая проходит по блокам (касается блокчейна
Viz, куда добавляются данные).
Функция run
let bn = 0;
async function run() {
const props = await viz.api.getDynamicGlobalPropertiesAsync();
bn = bn || props.last_irreversible_block_num;
for(; bn < props.last_irreversible_block_num; bn++){
await processBlock(bn);
}
}
Как видите, перед ней создаётся bn — это переменная, указывающая номер
блока, с которого производить парсинг блокчейна.
Константа props вызывает специальный метод, в котором находится
последний необратимый блок (блок, который не будет
откачен/изменён).
bn = bn || props.last_irreversible_block_num;
Эта строка получает непосредственно такой блок.
Ниже находится цикл, проходящий от текущего блока (в bn) до последнего
необратимого.
await processBlock(bn); вызывает функцию обработки операций, передаётся
в неё номер блока.
Функция processBlock
async function processBlock(bn) {
console.log("обработка блока",bn); // Вывод в консоль номера
обрабатываемого блока
const block = await viz.api.getBlockAsync(bn); // Получение операций
блока.
for(let tr of block.transactions) { // Цикл перебора транзакций в
блоке
for(let operation of tr.operations) { // Цикл прохода по операциям
const [op, opbody] = operation; // Назначаем переменные названию
операции (op) и основной части, содержащей различные данные
(opbody)
switch(op) { // Условие: если op
case "custom": // равно custom
if(opbody.id === 'istr-i52') { // Идёт проверка, что id в opbody равна
istr-i52
await processCustom(block.timestamp, opbody); // Выполнить функцию
processCustom, передать в неё дату и время блока и тело операции.
}
break; // Прерывание цикла
default: // Пустое значение по умолчанию
//неизвестная команда
}
}
}
}
Блок состоит из транзакций. Транзакции — это то, что отправляет
конкретный пользователь и подписывает своим ключом. Транзакции
содержат в себе операции (их может быть множество, но они должны
использовать один и тот-же тип ключа). Так вот: Методист или
преподаватель добавляет какие-то данные: отправляется транзакция с
ними, содержит операции custom с нужными сведениями, например, о новом
предмете.
Функция получает номер блока, обращается через специальный метод и
получает транзакции, проходит циклом по ним, а при каждой итерации
этого цикла запускает ещё и цикл прохода по операциям. В каждой итерации
цикла операций происходит обработка того, что там есть: названия и тела
операции. Тело, в свою очередь, имеет множество полей (они зависят от
того, что-за операция). В custom есть поле id, по которому и
идентифицируем нужные нам данные.
В конечном итоге, если id равен нужному значению, timestamp создания
блока и тело операции передаётся функции processCustom.
Функция processCustom
async function processCustom(timestamp, custom_body) {
let json = JSON.parse(custom_body.json); // Переводим JSON в js
массив;
const table_id = json.table; // Получаем имя таблицы, чтобы знать, куда
добавлять данные.
const data = json.data; // Получаем сами данные.
let lactors = await db.getData("lactors", "login",
custom_body.required_regular_auths[0]); // // Ищем в поле login
таблицы lactors логин из специального поля операции.
if (lactors[0].login === custom_body.required_regular_auths[0] &&
table_id === 'assessments' || table_id === 'lesson_topics') { //
Проверяем, что логин совпадает, а также таблица с оценками или
темами предметов.
let res = await db.addData(table_id, data); // Добавляем в базу данных
данные в таблицу из table_id.
console.log(res); // Выводим результат.
} else if (custom_body.required_regular_auths[0] ===
config.metodist) { // Если логин из операции равен значению логина из
конфига.
let res = await db.addData(table_id, data); // Добавляем данные в
базу.
console.log(res); // И выводим в консоль результат.
}
}
Пояснение: Тут думаю всё понятно. Единственное, скажу, что custom – это
операция отправки любых JSON данных в БЧ., поэтому их необходимо
преобразовывать.
Db – это модуль базы данных.
Общий вид JSOn – это примерно следующее:
[table: “table_id”, data: {}].
runActions
Самая простая функция:
async function runActions() {
let result_create = await db.createTables();
console.log(result_create);
}
Вызывается функция из модуля db, которая создаёт таблицы, если их нет.
Также выводится результат.
В начале файла
const config = require('./config.json'); // Подключение конфиг файла.
const db = require("./database"); // Модуль базы данных.
const viz = require("viz-world-js"); // Подключение npm пакета блокчейна
Viz.
Файл database.js
В конце экспортируются функции, чтобы с ними можно было работать после
подключения модуля в других файлах:
module.exports.addData = addData; // Экспорт метода, добавляющего данные
в базу.
module.exports.getData = getData; // Функция получения данных из БД.
module.exports.createTables = createTables; // Экспорт функции создания
таблиц
Выше располагаются функции. Давайте пройдёмся по ним:
createTables (создание таблиц)
function createTables() {
let sql = "CREATE TABLE IF NOT EXISTS [lactors] (id integer NOT NULL
AUTO_INCREMENT, login text); CREATE TABLE IF NOT EXISTS [disciples]
(id integer NOT NULL AUTO_INCREMENT, login text); CREATE TABLE IF NOT
EXISTS [lessons] (id integer NOT NULL AUTO_INCREMENT, name text,
lactors text); CREATE TABLE IF NOT EXISTS [lesson_topics] (id integer
NOT NULL AUTO_INCREMENT, date date, lesson text, topics text); CREATE
TABLE IF NOT EXISTS [assessments] (id integer NOT NULL
AUTO_INCREMENT, date date, lesson text, disciple text, assessment
integer)."; // Переменная с SQL запросом на создание таблиц lactors,
disciples, lessons, lesson_topics и assessments, если их нет.
return new Promise((resolve, reject) 😕> { // возвращаем результат в
Promise, чтобы можно было функцию использовать, как async (вызывать
через await).
connection.query(sql, (error, result) 😕> { // Отправляем запрос.
if(error) {
reject(error); // Если ошибка, возвращаем её.
} else {
resolve(result); // Иначе, результат.
}
});
});
}
getData
function getData(table_id, filde, value) { // Передаётся ей id таблицы,
поле и информация, которую в этом поле надо найти.
return new Promise((resolve, reject) 😕> {
connection.query('SELECT * FROM ' + table_id + '\ WHERE ' + filde
- '\ = ' + value, (error, results, fields) 😕> { // Делаем запрос с
использованием переменных, которые были переданы функции.
if(error) {
reject(error);
} else {
resolve(results);
}
});
});
}
Функция addData
function addData(table_id, data) { // передаются ей название таблицы и
данные.
return new Promise((resolve, reject) 😕> {
connection.query('insert INTO ' + table_id + ' SET ?', data, (error,
result, fields) 😕> { // Запрос с использованием переменных. Data
(данные) передаются в виде объекта javascript.
if(error) {
reject(error);
} else {
resolve(result);
}
});
});
}
В нач
але файла
const config = require('./config.json');
const mysql = require('mysql'); // Подключение NPM пакета Mysql.
let connection = mysql.createPool({
host : config.db_host,
user : config.db_login,
password : config.db_password
, database : config.db_name,
connectionLimit: 30
}); // Данные подключения к БД берутся из конфиг файла. Сюда они
забиваются, чтобы можно было использовать Mysql.
Package.json – файл с информацией о npm пакетах, которые нужны проекту
{
"dependencies": {
"mysql": "^2.16.0",
"viz-js-lib": "latest"
}
}
Из кода сего файла видно, что команда npm install в папке журнала
устанавливает mysql и viz-js-lib.
Всё
Благодарю за внимание. С вами был незрячий автор, программист и делегат @denis-skripnik. До встречи в новых постах.