Инженер-программист: г. Иркутск. Я специалист в области информационных технологий. У меня более 5 лет опыта в администрировании серверов различных ОС и в разработки скриптов, ПО и БД. Для саморазвития я в свободное время читаю и перевожу техническую документацию на английском языке, администрирую свой веб-сервер и дорабатываю свои проекты https://github.com/it38dato. Мне интересно работать в команде, активно развиваться в программировании и изучать новые технологии.
Навыки: Html, Css, Windows, Виртуализация, Linux, Sql, Bash, Clouds, Python, English, Etl-процессы, Dwh, AntiFraud, Спутниковые радионавигационные системы, С++.
Обратная связь: it38dato@yandex.ru, telegram - @it38dato.
Услуги:
# Верстка Веб-страниц по макетам.
# Администрирование локальных, виртуальных и облачных серверов.
# Администрирование Веб-сервера Lamp.
# Администрирование Веб-сервера Django.
# Администрирование Веб-сервера React.js.
# Администрирование базы данных.
# Разработка Веб-сайта Django.
# Разработка Веб-сайта React.js.
# Разработка Телеграм бота Переводчик.
# Разработка базы данных.
# Разработка Хранилище данных.
# Написание Sql запросов.
# Обработка и сортировка данных на Python, C++ и Sql-запросах.
# Анализ сетевых технологий.
2018-03-01 - 2022-11-01: Всероссийский государственный университет юстиции, Иркутск. Должность: Технический специалист. Дополнительная информация: Обязанности - работа с сайтами, поддержка функционирования серверов и сервисов СУБД, техническая поддержка пользователей / Навыки - Python, Виртуализация, Linux, Windows. Достижения: Разработал программу Обработка и сортировка Html-кода с целью упрощения корректировки тегов на сайте организации по запросам от руководства.
Show
Цель:
# Преобразовать текст в html код формат.
# Изменить тэги по по запросу руководства.
Skills:
# Обработка и сортирока Html-кода.
# Парсинг сайта.
Task:
В файле input.txt надо обычный текст преобразовать в html код формат, добавив только необходимые тэги, и записать результат в output.html.
# Обработка и сортирока Html-кода.
Decision:
root@kvmubuntu:~# vim py.py
root@kvmubuntu:~# cat py.py
#tag - теги html
#file - переменная файла
#fileread - считывание файла
#replacetags - замена тегов
#addtags - добавление тега p в файле
#tags=["<p>","</p>","<br>"," "]
tags=["<", ">", "<br>", " ", "<strong>Task:</strong>", "<strong>Decision:</strong>", "<strong>Source:</strong>"]
#print(tags[2])
with open("text.txt") as file:
fileread = file.read()
#print(fileread)
replacetags=fileread.replace("<", tags[0]).replace(">",tags[1]).replace("\n", tags[2]).replace(" ", tags[3]).replace("Task:", tags[4]).replace("Decision:", tags[5]).replace("Source:", tags[6])
#print(replacetags)
addtags = "<p>"+replacetags+"</p>"
with open("output.txt", "w") as file:
file.write(addtags+"\n")
Source:
# https://pythonworld.ru/tipy-dannyx-v-python/fajly-rabota-s-fajlami.html - Чтение из файла.
# https://docs-python.ru/tutorial/chtenie-zapis-fajl/odnovremennoe-chtenie-zapis-raznye-fajly/ - Работа с несколькими файлами в Python.
# https://sky.pro/media/chtenie-fajla-postrochno-v-spisok-na-python/ - Использование встроенной функции open().
# https://sky.pro/media/chtenie-fajla-postrochno-v-spisok-na-python/?ysclid=lv9i6ky8w2363195830 - Использование генераторов.
# https://translated.turbopages.org/proxy_u/en-ru.ru.8446d21d-66250fbc-1552d51e-74722d776562/https/stackoverflow.com/questions/54785152/replace-tab-with-space-in-entire-text-file-python?__ya_mt_enable_static_translations=1 - Заменить табуляцию пробелом во всем текстовом файле python.
# https://pythonturbo.ru/kak-v-python-dobavit-tekst-v-fajl/ - Добавление текста в файл с помощью оператора with
Task:
На странице https://tdomain.ru/sveden/education под поле "Образовательная программа, направленность, профиль, шифр и наименование научной специальности" в таблице "Образование" (информация по образовательным программам) необходимо добавить тег itemprop="eduProf". В данной таблице тег <tr itemprop="eduOp">, который нужно заменить на <tr itemprop="eduOprog">.
# Парсинг сайта.
Decision:
БУДЕТ КОД. ИНФОРМАЦИЯ БУДЕТ ДОПОЛНЯТЬСЯ. ШАГИ ВЫПОЛНЕНИЯ:
1. Прорамма будет апрашивать страницу для парсинга.
2. Парсинг страницы сайта по тегам
3. Поиск тега который нужно аменить
4. амена тега
5. Публикация именения на сайт
2011-09-01 - 2018-05-30: Иркутский государственный университет, Иркутск. Должность: Информационные технологии и телекоммуникационные системы - Бакалавр / Электроника и наноэлектроника - Магистр / Информационная безопасность - Дополнительное образование. Дополнительная информация: Навыки - Html, Css, Windows, Виртуализация, Linux, Sql, Bash, Clouds, Python, Спутниковые радионавигационные системы, С++. Достижения: Для защиты магистерской диссертации по теме "Использование данных одночастотных приемников Спутниковых Радионавигационных Систем для коррекции модели ионосферы" разработал программу "Обработка и сортировка данных в файлах Rinex формата"
Show
Цель:
# Рассмотреть возможность использования одночастотных приёмников спутниковых радионавигационных систем (СРНС) для получения информации с целью оперативной коррекции её модели.
# Установить и настроить программные обеспечения для обработки и сортировки данных.
# Получить данные с одночастотного приемника СРНС.
# Описать формат файлов RINEX.
# Разработать программу, которая считывает файлы в формате Rinex и сортирует псевдодальности (ПД) от времени (эпохи) по каждому навигационному спутнику.
# Получить результаты.
Skills:
# Спутниковые радионаввигационные системы.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Task:
Методика коррекции ионосферы по данному приёмнику СРНС.
# Спутниковые радионаввигационные системы.
Decision:
Рассмотрим возможность оценки вклада ионосферы в измерения псевдодальности (ПД) с помощью одночастотного приёмника ГЛОНАСС/GPS. Для этого будем считать, что координаты приемника x, y, z неизменны и известны с высокой точностью. ПД от i-го навигационного спутника (НС) с координатами xi, yi, zi определим как измеренную в свободном пространстве дальность D'i=сτ'i, отличающуюся от геометрической дальности (ГД) Di на неизвестную величину ρ:
D'i=сτ'i определяется по разности моментов приема и передачи навигационного сигнала между временными шкалами НС и приемника. Если точность шкалы НС с учетом её коррекции достаточна для определения момента излучения, то из-за нестабильности шкалы приемника необходимо учитывать её мгновенный сдвиг относительно шкалы НС на неизвестную величину t' и ρ=ct'. В многоканальных одночастотных приемниках t' не зависит от i и значение ρ одинаково для всех НС в данный момент времени [6].
В реальных условиях результаты измерения ПД D''i отличаются от значения D'i из формулы (4.1) из-за влияния различных факторов на распространение навигационных сигналов. Основные ошибки, возникающие при этом, вызваны запаздыванием сигнала в ионосфере и тропосфере , а также погрешностями определения эфемерид . Другие ошибки хорошо компенсируются алгоритмами обработки сигналов. Например, влияние многолучевости значительно уменьшается усреднением данных на интервале 30 с и более, а релятивистские эффекты устраняются коррекцией навигационного сигнала [6].
Таким образом, ошибка измерения ПД в основном определяется следующими составляющими:
Отсюда можно найти вклад ионосферы в ошибку измерения ПД для i-го НС:
Величина ρ в формуле (4.3) зависит от расхождения шкал времени НС и приемника и меняется в очень широких пределах.
При решении навигационной задачи относительно времени, шкала приемника может быть привязана к шкале НС, но найденная при этом величина ρ' будет характеризовать эффективное значение с учетом других ошибок определения ПД, и в этом случае будет иметь смысл отклонения от некоторого среднего значения для всех НС, участвующих в навигационном решении [6].
Для устранения влияния нестабильности временнόй шкалы приемника построим разность ошибок ионосферы для i-го и j-го НС в один момент времени:
Как видно, точность определения зависит от погрешностей определения эфемерид выбранных НС и разностей тропосферных задержек сигналов от них. обычно составляет единицы метров. Если из созвездия выбирать НС со «свежими» эфемеридами, то эта погрешность уменьшится до ~1 м. Применение постобработки и использование уточнённых эфемерид дает погрешность значительно меньше 1 м. Если предположить, что ошибки для двух НС независимы, а их величины примерно одинаковы, то общая ошибка из-за погрешностей задания эфемерид [6]:
Задержка в тропосфере приводит к ошибке в измерении ПД, которая хорошо компенсируется простыми моделями тропосферы. В нормальных условиях в тропосфере для углов места β > 5º и достижения погрешности после коррекции < 0.5 м можно воспользоваться формулой:
Здесь - задержка сигнала в тропосфере для зенитных НС, равная ~7 нс, что соответствует пути 2.1 м. Для увеличения точности коррекции можно применить модель Саастамойнена.
Таким образом, общая величина ошибки определения в благоприятных условиях составляет ~1÷2 м и вариации ионосферной задержки величиной ~10 и более метров могут быть определены одночастотным приемником ГНСС.
Task:
Получение данных с одночастотного приемника СРНС.
# Спутниковые радионаввигационные системы.
Decision:
Для проверки способа определения вариации ионосферной задержки 19 мая 2015 года провел обработку данных измерения навигационных сигналов от нескольких НС. Для измерений использовался 20 канальный одночастотный приемник BU-353 фирмы GlobalSat. Этот приемник предназначен для ГНСС GPS, работает на частоте L1 по C/A коду и имеет высокую чувствительность 159 дБм. Данные о псевдодальностях доступны только при использовании бинарного протокола, поэтому при наблюдениях применялась программа SirfTech версии 2.20 и данные записывались в RINEX файл. Описание программы SirfTech можно увидеть прил. 5.
Task:
Алгоритм обработки данных.
# Спутниковые радионаввигационные системы.
Decision:
Большая часть программного обеспечения обработки данных GPS использует определенный набор наблюдений:
- фазовые измерения на одной или двух несущих частотах;
- измерения псевдодальности (кода), которые соответствуют разности между временем получения и временем передачи отдельных сигналов спутника;
- время наблюдения считывается с часов приемника в момент измерения фазы несущей и/или кода.
При обработке необходимыми являются: фаза, код и время, определение которых дано выше, и некоторая информация относительно станции, такая как название станции, высота антенны и другие [5].
Task:
Описание формата RINEX.
# Спутниковые радионаввигационные системы.
Decision:
Для решении навигационной задачи исходными данными являются файлы в формате RINEX - независимый от приемника формат для обмена данными СРНС. RINEX - формат состоит из трех типов ASCII файлов - файлы метеорологических параметров (meteorological data file), файлы результатов измерений (observation data file), файлы с оперативной эфемеридной информацией, полученные в составе навигационного сообщения (navigation message file) [5].
Файлы имеют различную длину, максимальное значение равно 80 символам в строке (табл. 2). Каждый файл содержит секцию заголовков и секцию данных. Файл навигационного сообщения располагается независимо, в то время как файлы измерений и метеорологических данных должны быть созданы для каждого используемого при наблюдениях пункта [5].
Таблица 2. Общая структура формата RINEX:
Результаты измерений располагаются по эпохам. Каждой эпохе наблюдения соответствует структура, представленная на табл. 3 (количество и порядок расположения результатов различных типов измерений указывается в заголовке файла в строке "# / TYPES OF OBSERV", например, «7 LI L2C1 PI P2»), где у у mm dd hh mm sec момент приема сигнала по часам потребителя (tp); № - номера спутников, результаты наблюдений которых в соответствующем порядке записываются для каждой эпохи (при совместной обработке наблюдений ИСЗ обеих систем либо резервируются номера с 1 по 32 для спутников GPS и с 33 по 64 для спутников ГЛОНАСС, либо вводятся дополнительные признаки G и R); ИСЗ фаза L1 - псевдодальность, измеренная по фазе несущей на частоте L1; ИСЗ фаза L2 - псевдодальность, измеренная по фазе несущей на частоте L2; ИСЗ код С1 - псевдодальность, измеренная по С/А-коду на частоте L1; ИСЗ код Р1 - псевдодальность, измеренная по Р-коду на частоте L1; ИСЗ код Р2 - псевдодальность, измеренная по Р-коду на частоте L2 [2].
Таблица 3. Результаты СРНС-измерений на одну эпоху
Оперативная эфемеридная информация, полученная в составе навигационного сообщения, сгруппирована по номерам ИСЗ. Фрагменты файла измерений в формате RINEX представлены в прил. 2 [5].
GPS наблюдения включают в себя три основных понятия, которым необходимо дать определение: время, фаза и псевдодальность. Время измерений - это время приемника в момент приема сигналов. Оно одинаково для измерений фазы и псевдодальности и одинаково для всех наблюдаемых спутников в данную эпоху. Время выражается в единицах GPS-времени (не в мировом времени, UTC).
Псевдодальность (ПД) - это расстояние от приемной антенны до антенны спутника, включая сдвиги шкалы времени приемника и спутниковых часов (и другие сдвиги, такие как атмосферные задержки). Псевдодальность отражает реальное поведение часов приемника и передатчика. Псевдодальность указывается в единицах длины - метрах.
Фаза - это фаза несущей, измеренная в целых циклах. Измеряемое количество полуциклов квадратурными приемниками должно быть конвертировано в целые циклы и соответственно изменено значение длины волны в заголовке файла (только для GPS) [5].
Изменения фазы положительно коррелированны с изменениями дальности (негативный эффект Доплера). Фазовые наблюдения между эпохами должны быть скорректированы включением целого числа циклов. Фазовые наблюдения не будут содержать никаких систематических сдвигов от намеренных сдвигов опорных генераторов.
Данные всех измерений не скорректированы на внешние эффекты, такие как атмосферная рефракция, сдвиг часов спутникового передатчика и другие. Если приемник или программа конвертера производят измерения сдвига часов приемника в реальном времени dT(изм), то соответственно три параметра (фаза, псевдодальность, эпоха) должны быть исправлены. Знак доплеровского сдвига частоты определяется как обычно - положительный знак при приближении спутника [5].
Task:
Для дальнейшей обработки и сортировки данных мною была написана программа на языке С++, которая считывает файлы в RINEX формате и сортирует псевдодальности по каждому НС, что упрощает дальнейшую обработку. Для основной обработки данных по формуле 4.4 выполнялись вычисления с помощью редактора EXCEL.
Task:
Алгоритм.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
Из текстового файла вывести всю информацию до END OF HEADER в out.txt.
Вывести следующую строку после END OF HEADER - время и название спутников.
Вывести количество спутников и названия спутников в строке.
Вывести название спутников по буквам, то есть создаем динамический массив с указателем.
Вывести вторую строку название спутников.
Вывести буквы спутников в столбец.
Вывести по буквам и по цифрам. если увидим пробел, тогда операция не должна выполняться.
Вывести все спутники в столбец.
Вывести псевдодальности для одной/первой эпохи (время - 0:0).
Вывести названия спутников для первой эпохи.
Вывести псевдодальности для каждого спутника.
Вывести название спутника и расстояние (дальность) спутника. первая строка - для первой эпохи, вторая строка - для второй эпохи
Вывести для всех эпох данные спутников.
Вывести на экран данные для двух спутников.
Вывести данные именно для одного спутника, например первого.
Task:
Вывести названия спутников из файла для первой минуты.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat IRKL1119.txt
2.11 OBSERVATION DATA M (MIXED) RINEX VERSION / TYPE
...
END OF HEADER
11 11 19 0 0 0.0000000 0 18G 1G28R22G1R12G 7G 3G 6R 6R24R 5G26-0.000381360
G11G16R13R14G 8R23
23815384.399 23815383.399 125150709.498 3 3673.062 23.000
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
int countSputnik;
in >> countSputnik;
cout<< countSputnik<<endl ;
getline(in,name);
// cout<< name <<endl;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++)
{
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(index != countSputnik -1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
for(int i=0; i<countSputnik; i++){
out << allSputnik[i] << endl;
}
/*
delete [] Sputnik;
// ���������� ��������������� ��� ����� �����
double *information = new double [countSputnik];
for(int i=0; i<countSputnik; i++){
in >> information[i];
getline (in,name);
getline (in,name);
}
for(int i=0; i<countSputnik; i++){
out << information[i] << endl;
}
*/
/*
double *information = new double [countSputnik];
double information1;
for(int i=0; i<countSputnik; i++){
in >> information1;
in >> information[i];
getline (in,name);
getline (in,name);
}
for(int i=0; i<countSputnik; i++){
cout << information[i] << endl;
}
*/
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1
G28
R22
G1
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
Task:
Читаем Каждую Букву И Цифру После строки названия спутников для первой минуты.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat igs19236-1.txt
#cP2016 11 19 0 0 0.00000000 96 ORBIT IGb08 HLM IGS
...
* 2016 11 19 0 0 0.00000000
PG01 -20514.995126 -11031.142105 12884.404415 40.929190 7 8 4 99
PG02 12690.844622 22403.534044 7723.409365 523.430781 9 7 9 125
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <fstream>
#include <string.h>
#include <vector>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("igs19236-1.txt");
ofstream out ("out.txt");
// ���������� �� ORB:CMB CLK:CMB
string stroka;
for (int i=0; i<22; i++)
{
getline (in,stroka);
}
// ��������� �����
string epoha;
getline(in,epoha);
//out << epoha << endl;
/*char zvezda;
int data;
int vremia;
double sostoianie;
in >> zvezda;
in >> data;
in >> data;
in >> data;
in >> vremia;
in >> vremia;
in >> sostoianie;
//out << sostoianie;*/
// ��������� ������� ���� ��������� � �� ����������
/*
string position; // ��� ���� ������
getline (in,position);
out << position << endl;
*/
/*//������ ������� +
string sputnik; //������� ��������
string e; // EOF � * 2016 11 19
string x;
string y;
string z;
string signali; //������ �������
for (int j=0; j<96; j++){ //��� ���� ��������� � ��� ���� ����
for (int i=0; i<32; i++){ //��� ���� ��������� � ��� ����� �����
in >> sputnik >> x >> y >> z;
getline (in, signali);
out << sputnik << " " << x << " " << y << " " << z << " " << endl;
}
getline (in, e); // ��������� EOF � * 2016 11 19
}*/
/* //������ ������� -
int b;
//getline (in,position);
//out << position << endl;
string *info = new string [b];
for (int i=0; i<b; i++)
{
info[i]="";
}
int index=-1;
for(int i=0; i< position.size(); i++) {
if(position[i] >= 'A' && position[i] <= 'Z') {
index++;
}
if (position[i] !=' '){
info[index] = info[index] + position[i];
}
}
for(int i=0; i<b; i++)
{
out << " " << info[i] << " " << endl;
}
delete [] info;
*/
/*
double *information = new double [b];
for(int i=0; i<b; i++)
{
in >> information[i];
//getline (in,position);
//getline (in,position);
}
for(int i=0; i<b; i++){
out << information[i] << endl;
}
*/
string position; // ��� ���� ������
getline (in,position); // ����������� ��� ������ �������
//out << position << endl;
// ������� ������ ������� ���� ������
vector <string> infa; // ������� ������ ������ � ����� ������� �����
int index=-1; // ��������� ��������
string infaPosition = ""; // ������ ������� ��� ������ ��������
for(int i=0; i< position.size(); i++) { // ��� ������ ���� ������� ����� �������
if(position[i] >= 'A' && position[i] <= 'Z') { // 1 ���� ��������� �����
index++; // �������� ���������
/*if(infaPosition != "") { // ����
infa.push_back(infaPosition);
infaPosition="";
} */
}
/*if (position[i] != ' '){
infaPosition = infaPosition + position[i];
}*/
out << position[i] << endl; // ������� � ������ ������ �������
}
/* string a;
getline (in,a);
out << a << endl;
*/
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat out.txt
P
G
0
1
-
2
0
5
1
4
.
9
9
5
1
2
6
-
1
1
0
3
1
.
1
4
2
1
0
5
1
2
8
8
4
.
4
0
4
4
1
5
4
0
.
9
2
9
1
9
0
7
8
4
9
9
Task:
Мы Выводим Данные Спутников G16 В Текстовый Файл для каждой эпохи.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat IRKL1119.txt
...
G11G16R13R14G 8R23
23815384.399 23815383.399 125150709.498 3 3673.062 23.000
23815389.819 97520038.895 3 2862.131 23.000
23418101.271 23418101.711 123062972.555 4 2734.873 26.000
23418102.171 95893253.756 4 2131.058 26.000
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(53);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
for(int i=0; i<2880; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
/*out << countSputnik << endl; */
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/*
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
*/
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
/*
in >> dalnost;
in >> dalnost;
*/
string b = "";
getline (in, b);
a ="";
if(b.size()>=30){
a=b.substr(17,13);
}
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
if (a[i]>='0' && a[i]<='9'){
dalnost = dalnost *10 + (a[i]-'0');
}
else {
if(a[i]== '.') {
tochka=i;
}
}
}
if(tochka != -1){
chislo = a.size()-1 - tochka;
chislo = (int) pow(10, (double)chislo);
dalnost=dalnost/chislo;
}
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
}
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
out << endl;
for(int i=0; i<info.size(); i++){
out << setw(13) << setprecision(13) << info[i][0] << setw(14) << info[i][1] << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
...
23418101.711
23402498.509
23386912.128
23371345.657
23355798.957
...
Task:
Выводим список спутников и их псевдоальность для первой эпохи.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
int countSputnik;
in >> countSputnik;
//out<< countSputnik<<endl ;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
getline(in,name);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ���������
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
//out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setprecision(12) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G9 R23
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Task:
Выводим количество спутников, название спутников и пссевдодальность для каждого спутника за первые 15 минут.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
int countSputnik;
in >> countSputnik;
cout<< countSputnik<<endl ;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
getline(in,name);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
/*
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setprecision(12) << info[i][j] << " ";
}
out << endl;
}
*/
////////////////////////////////////////////////////////////////////
//getline(in, name);
//out << endl << name << endl;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
out << countSputnik << endl;
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G9
R23
19472155.069 81015603.872 8 1314.191 51.000
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
R23
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G9 R23 G8
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
23794425.041 23402498.509 20141642.176 20584598.589 20869652.326 20367330.686 22196301.185 23890335.007 23883323.395 22925234.933 23334086.485 23188096.286 21881612.664 24830710.969 19148858.027 21645612.101 0 19462739.332 20642124.208 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
/*
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
cout<< countSputnik<<endl ;
getline(in,name);
allSputnik.resize(0);// ������ ������ � ������� ����������
*/
/*
int index=-1; //��������� ��������
string tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
*/
/*
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setprecision(12) << info[i][j] << " ";
}
out << endl;
}
*/
////////////////////////////////////////////////////////////////////
//getline(in, name);
//out << endl << name << endl;
for(int i=0; i<4; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
out << countSputnik << endl;
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost;
in >> dalnost;
in >> dalnost;
getline (in,name);
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G9
R23
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
R23
18
G1
G28
R22
G19
R12
G7
G3
G6
R6
R24
R5
G26
G11
G16
R13
R14
G8
R23
1243988
19453454.30480937797.92821279.27712.000
19453454.30480937797.92821279.27712.000
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G9 R23 G8 19453454.30480937797.92821279.27712.000
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
23794425.041 23402498.509 20141642.176 20584598.589 20869652.326 20367330.686 22196301.185 23890335.007 23883323.395 22925234.933 23334086.485 23188096.286 21881612.664 24830710.969 19148858.027 21645612.101 0 19462739.332 20642124.208 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
23773491.181 23386912.128 -2723.261 20592788.59 20888815.333 20373592.145 22215405.514 23909502.132 23869784.311 22901490.193 23338082.579 23181639.852 21865044.244 24849525.498 19146493.433 21624473.283 0 19453451.974 20633403.301 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 19453451.974 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Task:
Выведем данные Псевдодальности для каждого спутника.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat IRKL1119.txt
2.11 OBSERVATION DATA M (MIXED) RINEX VERSION / TYPE
...
END OF HEADER
11 11 19 0 0 0.0000000 0 18G 1G28R22G19R12G 7G 3G 6R 6R24R 5G26-0.000381360
G11G16R13R14G 8R23
23815384.399 23815383.399 125150709.498 3 3673.062 23.000
23815389.819 97520038.895 3 2862.131 23.000
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(100);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
for(int i=0; i<2880; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
/*out << countSputnik << endl; */
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/*
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
*/
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
/*
in >> dalnost;
in >> dalnost;
*/
string b = "";
getline (in, b);
a ="";
if(b.size()>=30){
a=b.substr(17,13);
}
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
if (a[i]>='0' && a[i]<='9'){
dalnost = dalnost *10 + (a[i]-'0');
}
else {
if(a[i]== '.') {
tochka=i;
}
}
}
if(tochka != -1){
chislo = a.size()-1 - tochka;
chislo = (int) pow(10, (double)chislo);
dalnost=dalnost/chislo;
}
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
}
out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1 G28 R22 G19 R12 G7 G3 G6 R6 R24 R5 G26 G11 G16 R13 R14 G8 R23 G15 G22 G17 R7 R15 R17 R8 G32 G20 G9 R1 R16 G4 R18 R2 G12 G2 R9 G10 G23 G25 R19 R10 G13 G5 R4 G31 R11 G29 R20 G30 G21 R21 G18 G14
23815383.399 23418101.711 20126478.966 20576502.049 20850572.801 20361179.631 22177242.661 23871188.467 23896969.354 22949017.462 23330231.749 23194678.499 21898256.651 24811902.872 19151353.321 21666798.485 20650945.821 19472152.449 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
23794425.041 23402498.509 20141642.176 20584598.589 20869652.326 20367330.686 22196301.185 23890335.007 23883323.395 22925234.933 23334086.485 23188096.286 21881612.664 24830710.969 19148858.027 21645612.101 20642124.208 19462739.332 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
...
Task:
Выведем данные из файла Sirftin.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat sirfrin1.txt
2.10 OBSERVATION DATA G (GPS) RINEX VERSION / TYPE
...
END OF HEADER
15 5 18 0 41 23.0000613 0 11G 1G12G32G24G22G14G11G 4G18G31G17
23527987.056 8
21972648.359 8
22403365.951 6
23784621.452 7
21148286.543 6
20408196.378 4
24488332.428 4
23108442.059 5
23196661.017 4
22514486.129 5
25648439.341 3
15 5 18 0 41 24.0000613 0 11G 1G12G32G24G22G14G11G 4G18G31G17
23527900.192 8
21972620.569 8
22402887.237 5
23785272.449 7
21148685.515 6
20408150.444 4
24488620.925 4
23108699.683 5
23197296.183 4
22513871.405 5
25648503.069 3
...
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("sirfrin1.txt");
ofstream out ("output.txt");
//////// ������� END OF HEADER
string a;
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
//////// ����
string name; // ������� ������
int temp; // �����
double temp2; // ����� � ������
int countSputnik; // ���������� ���������
vector <vector <double> > info; // ������� ������
vector <double> tempVector;
tempVector.resize(15); // ������ �������
vector <string> Sputnik; // ������� ������� �� ���������
vector <string> allSputnik; // ������� ������� �� ����� ����������
int index=-1; // ��������� ��������
string tempName = ""; // ������ �������� ���������
//////// ������� ������� � ���������
for(int i=0; i<4797; i++) { // ������� ����� ����
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
// out << countSputnik << endl; // ���������� ���������
//////////////// ��������� �� �����
getline(in,name);
// out << name; // �������� ���������
//////////////// ��������� �������� ���������
allSputnik.resize(0);
index=-1;
tempName = "";
for(int i=0; i< name.size(); i++) {
if(name[i] == '-') // ���� ��������� -
break; // �� ���������
if(name[i] >= 'A' && name[i] <= 'Z') { // ���� ��������� �����
index++; // �� ��������� ������
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
//tempName="";
//////////////// ������� ��� ��������� ��������� ������ ������
/* for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
} */
//////////////// 2�� ������ �������� ���������
/*if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//getline(in,name); // ��������� ������ �� �����
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/* for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl; // ������� ��� ��������� ��������� � ������ � ������ ������
} */
/////////////// ��������� ���������� ���������
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
string b = "";
getline (in, b);
a ="";
if(b.size()>=14){
a=b.substr(2,12); // �������� ������ ��� ����������
}
// out << a << endl; // ������� ���������� ���������
/////////////////////// ����������� ������ ������� � ������� ��� ���� ����� ���������� ������ ������
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
if (a[i]>='0' && a[i]<='9'){
dalnost = dalnost *10 + (a[i]-'0');
}
else {
if(a[i]== '.') {
tochka=i;
}
}
}
if(tochka != -1){
chislo = a.size()-1 - tochka;
chislo = (int) pow(10, (double)chislo);
dalnost=dalnost/chislo;
}
/////////////////////// ��������� �������� � ��������� � ������������
// getline (in,name); //��������� �� ����� ����� ������
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
}
//////// ������� ���������� �� �������
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
/////// ������� ���������� �� �������
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
}
//---------------------------------------------------------------------------
root@kvmubuntu:~# cat output.txt
G1 G12 G32 G24 G22 G14 G11 G4 G18 G31 G17 G3 G25 G26 G29
23527987.056 21972648.359 22403365.951 23784621.452 21148286.543 20408196.378 24488332.428 23108442.059 23196661.017 22514486.129 25648439.341 0 0 0 0
23527900.192 21972620.569 22402887.237 23785272.449 21148685.515 20408150.444 24488620.925 23108699.683 23197296.183 22513871.405 25648503.069 0 0 0 0
23527815.469 21972594.92 22402410.598 23785925.499 21149086.34 20408107.268 24488915.359 23108959.419 23197938.791 22513258.71 25648554.131 0 0 0 0
23527729.677 21972568.22 22401932.854 23786577.382 21149485.828 20408062.76 24489187.365 23109217.855 23198578.688 22512592.942 25648601.174 0 0 0 0
...
Task:
Нарисуйте График с файла Sirfrin1.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("sirfrin1.txt");
ofstream out ("output.txt");
//////// ������� END OF HEADER
string a;
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
//////// ����
string name; // ������� ������
int temp; // �����
double temp2; // ����� � ������
int countSputnik; // ���������� ���������
vector <vector <double> > info; // ������� ������
vector <double> tempVector;
tempVector.resize(15); // ������ �������
vector <string> Sputnik; // ������� ������� �� ���������
vector <string> allSputnik; // ������� ������� �� ����� ����������
int index=-1; // ��������� ��������
string tempName = ""; // ������ �������� ���������
//////// ������� ������� � ���������
for(int i=0; i<4797; i++) { // ������� ����� ����
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
// out << countSputnik << endl; // ���������� ���������
//////////////// ��������� �� �����
getline(in,name);
// out << name; // �������� ���������
//////////////// ��������� �������� ���������
allSputnik.resize(0);
index=-1;
tempName = "";
for(int i=0; i< name.size(); i++) {
if(name[i] == '-') // ���� ��������� -
break; // �� ���������
if(name[i] >= 'A' && name[i] <= 'Z') { // ���� ��������� �����
index++; // �� ��������� ������
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
//tempName="";
//////////////// ������� ��� ��������� ��������� ������ ������
/* for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
} */
//////////////// 2�� ������ �������� ���������
/*if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//getline(in,name); // ��������� ������ �� �����
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/* for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl; // ������� ��� ��������� ��������� � ������ � ������ ������
} */
/////////////// ��������� ���������� ���������
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
string b = "";
getline (in, b);
a ="";
if(b.size()>=14){
a=b.substr(2,12); // �������� ������ ��� ����������
}
// out << a << endl; // ������� ���������� ���������
/////////////////////// ����������� ������ ������� � ������� ��� ���� ����� ���������� ������ ������
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
if (a[i]>='0' && a[i]<='9'){
dalnost = dalnost *10 + (a[i]-'0');
}
else {
if(a[i]== '.') {
tochka=i;
}
}
}
if(tochka != -1){
chislo = a.size()-1 - tochka;
chislo = (int) pow(10, (double)chislo);
dalnost=dalnost/chislo;
}
/////////////////////// ��������� �������� � ��������� � ������������
// getline (in,name); //��������� �� ����� ����� ������
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
}
//////// ������� ���������� �� �������
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
/////// ������� ���������� �� �������
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
out << endl;
for(int i=0; i<info.size(); i++){
out << setw(13) << setprecision(13) << info[i][0] << setw(14) << info[i][1] << endl;
Form1->Memo1->Lines->Add(info[i][0]);
Form1->Memo2->Lines->Add(info[i][1]);
Form1->Memo3->Lines->Add(info[i][0]-info[i][1]);
//Series1->Add(info[i][0],clBlue);
//Series2->Add(info[i][1],clBlue);
Series2->Add(info[i][0]-info[i][1],clBlue);
}
}
//---------------------------------------------------------------------------
Task:
Нарисуйте График И Вычислите Разницу.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
root@kvmubuntu:~# cat Unit1.cpp
//---------------------------------------------------------------------------
#include <iostream>
#include <vector>
#include <fstream>
#include <string.h>
#include <iomanip.h>
#include <cmath>
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ifstream in ("IRKL1119.txt");
ofstream out ("output.txt");
string a;
//���������� �� �� END OF HEADER
while(true) {
getline (in,a);
if(a == " END OF HEADER") {
break;
}
}
string name;
// ������� ������� � ���������
int temp;
double temp2;
int countSputnik;
vector <vector <double> > info;
vector <double> tempVector;
tempVector.resize(53);
vector <string> Sputnik;
vector <string> allSputnik;// ������ ������ � ������� ����������
int index=-1; //��������� ��������
string tempName = "";
for(int i=0; i<2880; i++) {
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp;
in >> temp2;
in >> temp;
in >> countSputnik;
/*out << countSputnik << endl; */
getline(in,name);
allSputnik.resize(0);
index=-1; //��������� ��������
tempName = ""; //
for(int i=0; i< name.size(); i++) {
if(name[i] == '-')
break;
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
tempName="";
if(index <= countSputnik-1) { //��������� ��������� �� ����� ���� ��������
//2 stroka
getline(in,name);
for(int i=0; i<name.size(); i++){
if(name[i] >= 'A' && name[i] <= 'Z') {
index++;
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
}
if (name[i] != ' '){
tempName = tempName + name[i];
}
}
}
if(tempName != "") {
allSputnik.push_back(tempName);
tempName="";
}
/*
for(int i=0; i<allSputnik.size(); i++){
out << allSputnik[i] << endl;
}
*/
info.push_back(tempVector);
for(int i = 0; i<allSputnik.size(); i++) {
double dalnost=0;
/*
in >> dalnost;
in >> dalnost;
*/
string b = "";
getline (in, b);
a ="";
if(b.size()>=14){
a=b.substr(1,13);
}
int tochka=-1;
int chislo=0;
for (int i=0; i<a.size(); i++){
if (a[i]>='0' && a[i]<='9'){
dalnost = dalnost *10 + (a[i]-'0');
}
else {
if(a[i]== '.') {
tochka=i;
}
}
}
if(tochka != -1){
chislo = a.size()-1 - tochka;
chislo = (int) pow(10, (double)chislo);
dalnost=dalnost/chislo;
}
getline (in,name);
index=-1;
for(int j=0; j<Sputnik.size(); j++) {
if(allSputnik[i] == Sputnik[j]) {
index = j;
break;
}
}
if(index == -1) {
Sputnik.push_back(allSputnik[i]);
info[info.size()-1][Sputnik.size()-1] = dalnost;
}
else {
info[info.size()-1][index] = dalnost;
}
}
}
// out << endl;
for(int j=0; j<Sputnik.size(); j++) {
out << setw(13) << Sputnik[j] << " ";
}
out << endl;
for(int i=0; i<info.size(); i++) {
for(int j=0; j<info[i].size(); j++) {
out << setw(13) << setprecision(13) << info[i][j] << " ";
}
out << endl;
}
out << endl;
for(int i=0; i<info.size(); i++){
Form1->Memo1->Lines->Add(info[i][0]);
Form1->Memo2->Lines->Add(info[i][1]);
//Form1->Memo3->Lines->Add(info[i][0]-info[i][1]);
Series1->Add(info[i][0],clBlue);
Series2->Add(info[i][1],clBlue);
//out << setw(13) << setprecision(13) << info[i][0] << setw(14) << info[i][1] << endl;
}
}
//---------------------------------------------------------------------------
Task:
Попробуем просто сделать это все на графическом интерфейсе, вывести данные двух спутников, плюс еще нарисовать графики этих данных для двух спутников.
Теперь нужно обработать и сортировать данные с геометрической дальностью для спутников. Файл называется igr18451.txt. Вот как выглядят данные, которые нужно отсортировать. Тут уже одна эпоха равняется 15 минутам.
То что я выделил, это координаты спутника X,Y,Z. Именно их и нужно отсортировать. PG01 - название первого спутника. Попробуем вывести данные именно для него.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
PG04 16571.755106 7091.147720 19268.134634
PG04 17081.817393 9222.831883 17918.904800
PG04 17633.922784 11162.951350 16256.328779
PG04 18194.986791 12883.319207 14310.615388
PG04 18728.745598 14363.489413 12116.696076
PG04 19197.262350 15591.290270 9713.525830
PG04 19562.489921 16563.032391 7143.333216
PG04 19787.827341 17283.391677 4450.837454
PG04 19839.609715 17764.980464 1682.449322
PG04 19688.476298 18027.631365 -1114.528992
PG04 19310.568086 18097.427949 -3892.691773
PG04 18688.514262 18005.524210 -6605.294077
PG04 17812.175758 17786.800613 -9206.983073
PG04 16679.123638 17478.408399 -11654.488894
PG04 15294.839581 17118.255856 -13907.269035
PG04 13672.635233 16743.490595 -15928.101701
PG04 11833.296299 16389.030589 -17683.624513
PG04 9804.465824 16086.194088 -19144.815686
PG04 7619.789018 15861.474593 -20287.415200
...
Task:
Это мы вывели координаты X,Y,Z и название спутника. Но мне теперь нужно вывести данные только Х для первого спутника.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
14736.411108
15414.203154
16201.304785
17074.555745
18004.439905
18956.228081
19891.328698
20768.792721
21546.914135
...
Task:
Аналогично выводятся только для Y и Z выводим данные. Просто нужно поменять в коде x на y или z.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Task:
Аналогично нужно вывести данные псевдодальности полученные с моего приемника. файл - sirfrin1.txt.
Тут мы увидим только две данные для спутников. Первые, это дальность, которую как раз и нужно отсортировать по каждом НС, а вторые - сигналы спутника, которые нас не особо не интересуют. А также эпоха здесь начинается не с нуля, как мы видим, а с 41 минуты 23 секунды. Это тоже нужно учитывать, когда нужно будет сравнивать эти данные с ГД.
# Разработка программы Обработка и сортировка данных в файлах Rinex формата.
Decision:
Мы задачу выполнили (отсортировать данные ГД и ПД по каждом навигационному спутнику), остается только обработать эти данные по формулам и посторить по ним графики через Эксель.
Task:
Экспериментальные результаты.
# Спутниковые радионаввигационные системы.
Decision:
Для наглядности на рис. 14 показан пример измерения навигационных сигналов, принимаемых с НС GPS1 и GPS20, взятый из статьи [6]. По горизонтали отложены номера 30-ти секундных интервалов (эпох) от 0 часов всемирного времени. В верхней части рисунка даны результаты вычислений углов места НС GPS1 (пунктир) и GPS20 (точки). В нижней части рисунка тонкой линией в соответствии с формулой (3.4) показана разность измеренных псевдодальностей за вычетом разности ГД от этих НС с коррекцией запаздывания лучей в тропосфере по формуле (4.5). Для расчета ГД использовались уточненные эфемериды SP3 [6].
Рис. 14. Пример измерений вариаций ионосферы:
Здесь же для сравнения жирной линией приведена разность ионосферных ошибок для этих же НС по данным двухчастотного приемника JPS EGGDT станции Иркутск (IRKL).
На рис. 15 показана средняя часть рис. 14 для детального сравнения данных о вариациях ионосферной ошибки, определяемой вариациями ПЭС между двумя НС [6].
Рис.15. Сравнение данных одночастотного и двухчастотного приемников:
Видно, что для высоких углов места в среднем наблюдается хорошее совпадение результатов, полученных разными методами. Следует отметить, что случайные отклонения у одночастотного приемника примерно в два раза больше. При приближении НС к горизонту данные одночастотного приемника начинают сильно отличаться от двухчастотного, и при углах места около 5° различие доходит до 15-20 м (~ 60 нс). Вероятно, это связано с влиянием тропосферы и неучтенной при коррекции «влажной» составляющей тропосферной погрешности [6].
Приведенные данные свидетельствуют о близости результатов определения разности ионосферных ошибок двухчастотным и одночастотным приемниками. Как видно из сравнения описанных результатов, в диапазоне углов места больше 15° две кривые хорошо совпадают, различие между ними в 90% случаев не превышает 2 м (7 нс), при регулярном ходе около 20 м (70 нс) [6].
Для сравнения, я пытался построить свои примеры измерения навигационных сигналов. В прил. 3 показан код программы на языке С++, которая обрабатывает и сортирует данные ПД по каждому НС. На табл. 4 показан пример вычислений и интерпретаций данных с помощью EXCEL:
Таблица 4. Вычисления данных с помощью Excel:
где x, y, z – координаты приёмника, а Xi, Yi, Zi – координаты спутника, ГД- вычисленная по формуле (4.1) геометрическая дальность, ПД – пседодальность и последний столбец является разностью ошибок ионосферы для двух спутников G1и G28 вычисленная по формуле (4.4).
На рис. 16-19 построены разности ошибок ионосферы для двух НС в один момент времени. Первая часть данных взяты из собственных измерений, а вторая часть данных для сравнения давались мне руководителем. По горизонтали отложены номера 15-ти минутных интервалов.
Рис. 16. Пример измерений, принимаемых с GPS 1 и GPS 28, Рис. 17. Пример измерений, принимаемых с GPS 20 и GPS 32, Рис. 18. Пример измерений, принимаемых с GPS 2 и GPS 5, Рис. 19. Пример измерений, принимаемых с GPS 1 и GPS 4:
Разность псевдодальности и геометрической дальности на рис. 16-17 является разностью ионосферной задержки и может быть использована для коррекции ионосферы. На рис. 18-19 получились большие разности, и такие данные использовать для уточнения состояния ионосферы при её оперативной коррекции нельзя.
Decision:
Для защиты диссертации по теме "Использование данных с одночастотных приемников спутниковых радионавигационных систем для коррекции модели ионосферы" освоил технологию приёма получения данных с одночастотных приемников спутниковых радионавигационных систем, получил данные, разработал программу на C++, которая обрабатывает и сортирует данные двух координат из файла по столбцам, рисует график, чтобы увидеть желаемый результат в точности определения координат спутников, рассмотрел способы уменьшения ошибок измерения псевдодальности и показал, что из-за нестабильности аппаратуры потребителя информация о состоянии ионосферы может быть получена в каждый момент времени по разностям ПД двух навигационных спутников
Decision:
Защитил диплом выпускной квалификационной работы бакалавра и магистерскую диссертацию.
Source:
# https://forum.calculate-linux.org/t/windows-qemu-kvm-libvirt/9357
# https://blog.sedicomm.com/2019/07/21/rdesktop-klient-rdp-dlya-podklyucheniya-rabochego-stola-windows-iz-linux/
# https://learn.microsoft.com/ru-ru/windows-server/administration/openssh/openssh_install_firstuse
# https://learn.microsoft.com/ru-ru/powershell/scripting/learn/remoting/ssh-remoting-in-powershell-core?view=powershell-7.3
# https://learn.microsoft.com/en-us/powershell/module/storage/dismount-diskimage?view=windowsserver2022-ps
# https://superuser.com/questions/499264/how-can-i-mount-an-iso-via-powershell-programmatically
# https://winitpro.ru/index.php/2016/03/31/sftp-ssh-ftp-na-windows-server-2012-r2/
# https://mhelp.pro/ru/kak-zapustit-powershell-ot-imeni-administratora/?ysclid=lmzwqntxfi559273426
# https://remontka.pro/text-files-cmd-powershell/?ysclid=lmzy2p5172409325479
# https://www.digitalocean.com/community/tutorials/sftp-ru
# http://ftp.glonass-iac.ru/guide/navfaq.php
# https://ru.wtuseripedia.org/wtuseri/%D0%A1%D0%BF%D1%83%D1%82%D0%BD%D0%B8%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D0%BD%D0%B0%D0%B2%D0%B8%D0%B3%D0%B0%D1%86%D0%B8%D0%B8
# Харисов В.Н. Глобальная спутниковая радионавигационная система ГЛОНАСС
# Грудинская Г.П. Распространение радиоволн
# http://begin.clan.su/news/speciftusera_provedenija_psevdodalnomernykh_i_fazov/2013-09-18-135
# http://rrv.iszf.irk.ru/sites/default/files/conf2014/articles/tom2/17-20.pdf
# http://www.u-blox.com
# Дэвис К. Радиоволны в ионосфере
# https://www.youtube.com/watch?v=bDvVosvyVp0&list=LL&index=315
2011-09-01 - 2018-05-30: Иркутский государственный университет, Иркутск. Должность: Информационные технологии и телекоммуникационные системы - Бакалавр / Электроника и наноэлектроника - Магистр / Информационная безопасность - Дополнительное образование. Дополнительная информация: Навыки - Html, Css, Windows, Виртуализация, Linux, Sql, Bash, Clouds, Python, Спутниковые радионавигационные системы, С++. Достижения: Для защиты выпускной экзаменационной работы по теме "Утилита для сканирования безопасности сети Nmap" подготовил несколько тестовых виртуальных машин ОС, настроил Vsftpd-сервер, Nginx-сервер, Proxy-сервер, просканировал виртуальные машины и настроил Iptables.
Show
Цель:
# Установить и настроить несколько виртуальных машин.
# Настроить Iptables в Centos.
# Анализ сетевых портов в Centos.
# Написать скрипт для набора правил iptables.
# Установить и настроить Ftp сервер.
# Установить и настроить Nginx сервер.
# Установить и настроить Proxy сервер.
# Анализ сетевых портов утилитой Nmap.
# Перехват трафиков утилитой Tcpdump.
# Блокировка подозрительных IP-адресов.
# Установить и настроить tripwire.
Skills:
# Администрирование локальных, виртуальных и облачных серверов.
# Написаниие скриптов.
# Анализ сетевых технологий.
Task:
Выводим список текущих правил iptables и проанализируем какие порты открыты в сервере Centos.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipcentos
[root@kvmcentos ~]# yum install iptables-services
[root@kvmcentos ~]# iptables --version
[root@kvmcentos ~]# iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
[root@kvmcentos ~]# systemctl start iptables
[root@kvmcentos ~]# systemctl enable iptables
[root@kvmcentos ~]# service iptables status
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipcentos
...
PORT STATE SERVICE
22/tcp open ssh
Task:
В сервере Centos Напишем набор правил iptables, в котором мы разрешаем все исходящие соединения и строго ограничиваем входящие.
Доступ будет возможен по портам TCP: 21, 22, 25, 53, 80, 143, 443, по портам UDP: 20, 21, 53, также мы пропускаем пакеты для уже установленных соединений.
С удаленной машины просканируем порты на нашем сервере.
# Написание скриптов.
Decision:
[root@kvmcentos ~]# vim firewall.sh
[root@kvmcentos ~]# cat firewall.sh
#!/bin/bash
IPT="/sbin/iptables"
# Очищаем правила и удаляем цепочки.
root@aw:/# IPT -F
root@aw:/# IPT -X
# По умолчанию доступ запрещен.
root@aw:/# IPT -P INPUT DROP
root@aw:/# IPT -P FORWARD DROP
root@aw:/# IPT -P OUTPUT DROP
# Список разрешенных TCP и UDP портов.
TCP_PORTS="21,22,25,53,80,143,443"
UDP_PORTS="53,21,20"
# Разрешаем пакеты для интерфейса обратной петли.
root@aw:/# IPT -A INPUT -i lo -j ACCEPT
root@aw:/# IPT -A OUTPUT -o lo -j ACCEPT
# Разрешаем пакеты для установленных соединений.
root@aw:/# IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Разрешаем исходящие соединения.
root@aw:/# IPT -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Разрешаем доступ к портам, описанным в переменных TCP_PORTS и UDP_PORTS.
root@aw:/# IPT -A INPUT -p tcp -m multiport --dport root@aw:/#TCP_PORTS -j ACCEPT
root@aw:/# IPT -A INPUT -p udp -m multiport --dport root@aw:/#UDP_PORTS -j ACCEPT
# Разрешаем исходящий ping.
root@aw:/# IPT -A INPUT -p icmp -m icmp --icmp-type echo-reply -j ACCEPT
[root@kvmcentos ~]# chmod +x firewall.sh
[root@kvmcentos ~]# ./firewall.sh
[root@kvmcentos ~]# iptables -L -v
Chain INPUT (policy DROP 1986 packets, 87384 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
79 5604 ACCEPT all -- any any anywhere anywhere state RELATED,ESTABLISHED
9 396 ACCEPT tcp -- any any anywhere anywhere multiport dports ftp,ssh,smtp,domain,http,imap,https
0 0 ACCEPT udp -- any any anywhere anywhere multiport dports domain,ftp,ftp-data
0 0 ACCEPT icmp -- any any anywhere anywhere icmp echo-reply
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- any lo anywhere anywhere
60 7920 ACCEPT all -- any any anywhere anywhere state NEW,RELATED,ESTABLISHED
[root@kvmcentos ~]# service iptables save
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipcentos
...
PORT STATE SERVICE
21/tcp closed ftp
22/tcp open ssh
25/tcp closed smtp
53/tcp closed domain
80/tcp closed http
143/tcp closed imap
443/tcp closed https
...
Source:
# https://blog.sedicomm.com/2016/12/16/iptables-ustanovka-i-nastrojka/?ysclid=ln3wplng53988958006#1
Task:
Обнаружим активные устройства в сети.
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ apt install nmap
┌──(tuser㉿kvmkali)-[~]
└─$ nmap -sL tipcentos/24
...
Nmap scan report for KvmKali (tipkali)
...
Nmap scan report for kvmcentos (tipcentos)
...
Nmap scan report for kvmubuntu (tipubuntu)
...
Task:
Просканируем хост и проанализируем порт ftp. В некоторых случаях можно вытащить логин и пароль. Такое происходит, когда используются параметры входа по умолчанию.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# nmap -sV tipcentos
...
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 8.7 (protocol 2.0)
80/tcp open http nginx 1.22.1
3128/tcp open http-proxy Squid http proxy 5.5
...
[root@kvmcentos ~]# nmap -sC tipcentos -p 21
...
PORT STATE SERVICE
21/tcp open ftp
...
root@aw:/# find /usr/share/nmap/scripts/ -name '*.nse' | grep ftp
...
/usr/share/nmap/scripts/ftp-brute.nse
...
root@aw:/# nmap --script-help ftp-brute.nse
...
Performs brute force password auditing against FTP servers.
...
[root@kvmcentos ~]# nmap --script ftp-brute.nse tipcentos -p 21
...
PORT STATE SERVICE
21/tcp open ftp
| ftp-brute:
| Accounts: No valid accounts found
| Statistics: Performed 324 guesses in 642 seconds, average tps: 7.5
|_ ERROR: The service seems to have failed or is heavily firewalled...
...
Task:
Сканируем диапазон портов.
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap -sT -p 21-80 tipcentos
...
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
25/tcp closed smtp
53/tcp closed domain
Task:
Просканируем удаленный хост (агрессивный режим).
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap -A -T4 tipcentos
...
PORT STATE SERVICE VERSION
20/tcp closed ftp-data
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 8.7 (protocol 2.0)
25/tcp closed smtp
53/tcp closed domain
143/tcp closed imap
443/tcp closed https
3128/tcp open http-proxy Squid http proxy 5.5
|_http-server-header: squid/5.5
|_http-title: ERROR: The requested URL could not be retrieved
MAC Address: tmaccentos (QEMU virtual NIC)
Aggressive OS guesses: Linux 2.6.32 - 3.13 (94%), Linux 2.6.22 - 2.6.36 (92%), Linux 3.10 (92%), Linux 3.10 - 4.11 (92%), Linux 2.6.39 (92%), Linux 2.6.32 (91%), Linux 3.2 - 4.9 (91%), Linux 2.6.32 - 3.10 (91%), Linux 2.6.18 (90%), Linux 3.16 - 4.6 (90%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 1 hop
Service Info: OS: Unix
TRACEROUTE
HOP RTT ADDRESS
1 0.83 ms tipcentos
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 24.18 seconds
Source:
# https://losst.ru/kak-polzovatsya-nmap-dlya-skanirovaniya-seti
Task:
Перехватываем DNS-трафик между сервером и каким-нибудь узлом в сети.
# Анализ сетевых технологий.
Decision:
┌──(tuser㉿kvmkali)-[~]
└─$ nmap tipwindows12
...
PORT STATE SERVICE
53/tcp open domain
80/tcp open http
443/tcp open https
...
[root@kvmcentos ~]# tcpdump -i enp1s0 -n -nn -ttt 'host tipwindows12 and port 53'
Task:
Перехватываем весь трафик для MAC-адреса tmaccentos на сетевом интерфейсе enp1s0.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# ifconfig
enp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
...
ether tmaccentos txqueuelen 1000 (Ethernet)
...
[root@kvmcentos ~]# tcpdump -n -i enp1s0 "ether host tmaccentos"
...
┌──(tuser㉿kvmkali)-[~]
└─$ ssh tuser@tipcentos
[root@kvmcentos ~]# tcpdump -n -i enp1s0 "ether host tmaccentos"
...
00:11:12.536934 IP tipcentos.58598 > tipubuntu.22: Flags [.], ack 3730, win 501, options [nop,nop,TS val 3107339434 ecr 2462889902], length 0
00:11:12.586762 IP tipubuntu.22 > tipcentos.58598: Flags [P.], seq 3730:3782, ack 2242, win 501, options [nop,nop,TS val 2462889993 ecr 3107339434], length 52
00:11:12.586838 IP tipubuntu.22 > tipcentos.58598: Flags [P.], seq 3782:3898, ack 2242, win 501, options [nop,nop,TS val 2462889993 ecr 3107339434], length 116
00:11:12.588205 IP tipcentos.58598 > tipubuntu.22: Flags [.], ack 3782, win 501, options [nop,nop,TS val 3107339485 ecr 2462889993], length 0
00:11:12.588207 IP tipcentos.58598 > tipubuntu.22: Flags [.], ack 3898, win 501, options [nop,nop,TS val 3107339486 ecr 2462889993], length 0
Task:
Перехватываем только ICMP-пакеты.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# tcpdump -i enp1s0 -n -nn -ttt 'ip proto \icmp'
...
┌──(tuser㉿kvmkali)-[~]
└─$ ping tipcentos
...
64 bytes from tipcentos: icmp_seq=1 ttl=64 time=0.692 ms
64 bytes from tipcentos: icmp_seq=2 ttl=64 time=0.764 ms
64 bytes from tipcentos: icmp_seq=3 ttl=64 time=0.868 ms
64 bytes from tipcentos: icmp_seq=4 ttl=64 time=1.01 ms
64 bytes from tipcentos: icmp_seq=5 ttl=64 time=1.13 ms
^C
...
[root@kvmcentos ~]# tcpdump -i enp1s0 -n -nn -ttt 'ip proto \icmp'
...
00:00:00.000000 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 1, length 64
00:00:00.000171 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 1, length 64
00:00:01.001145 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 2, length 64
00:00:00.000077 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 2, length 64
00:00:01.001365 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 3, length 64
00:00:00.000077 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 3, length 64
00:00:01.001375 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 4, length 64
00:00:00.000077 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 4, length 64
00:00:01.001573 IP tipcentos > tipubuntu: ICMP echo request, id 4, seq 5, length 64
00:00:00.000125 IP tipubuntu > tipcentos: ICMP echo reply, id 4, seq 5, length 64
Task:
Перехватываем входящий трафик на порт 80. сохраняем статистику в файл my.log для первых 500 пакетов. Будет создан бинарный файл my.log.
# Анализ сетевых технологий.
Decision:
[root@kvmcentos ~]# tcpdump -v -i enp1s0 dst port 80
...
┌──(tuser㉿kvmkali)-[~]
└─$ firefox tipcentos:80
[root@kvmcentos ~]# tcpdump -v -i enp1s0 dst port 80
...
14:07:24.835633 IP (tos 0x0, ttl 64, id 15549, offset 0, flags [DF], proto TCP (6), length 60)
kvmcentos.45022 > kvmubuntu.http: Flags [S], cksum 0x76d1 (incorrect -> 0x9a90), seq 1076682845, win 64240, options [mss 1460,sackOK,TS val 3110711737 ecr 0,nop,wscale 7], length 0
14:07:24.836511 IP (tos 0x0, ttl 64, id 15550, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [.], cksum 0x76c9 (incorrect -> 0x51b3), ack 2952605173, win 502, options [nop,nop,TS val 3110711738 ecr 3247231251], length 0
14:07:24.836838 IP (tos 0x0, ttl 64, id 15551, offset 0, flags [DF], proto TCP (6), length 412)
kvmcentos.45022 > kvmubuntu.http: Flags [P.], cksum 0x7831 (incorrect -> 0xb0a2), seq 0:360, ack 1, win 502, options [nop,nop,TS val 3110711739 ecr 3247231251], length 360: HTTP, length: 360
GET / HTTP/1.1
Host: tipubuntu
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
14:07:24.844920 IP (tos 0x0, ttl 64, id 15552, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [.], cksum 0x76c9 (incorrect -> 0x4285), ack 3522, win 489, options [nop,nop,TS val 3110711747 ecr 3247231260], length 0
14:07:29.846014 IP (tos 0x0, ttl 64, id 15553, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [F.], cksum 0x76c9 (incorrect -> 0x2eef), seq 360, ack 3522, win 501, options [nop,nop,TS val 3110716748 ecr 3247231260], length 0
14:07:29.846795 IP (tos 0x0, ttl 64, id 15554, offset 0, flags [DF], proto TCP (6), length 52)
kvmcentos.45022 > kvmubuntu.http: Flags [.], cksum 0x76c9 (incorrect -> 0x1b63), ack 3523, win 501, options [nop,nop,TS val 3110716749 ecr 3247236262], length 0
[root@kvmcentos ~]# tcpdump -v -n -w my.log dst port 80 -c 500
...
[root@kvmcentos ~]# ls my.log
my.log
[root@kvmcentos ~]# tcpdump -nr my.log | awk '{print root@aw:/#3}' | grep -oE '[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}' | sort | uniq -c | sort -rn
reading from file my.log, link-type EN10MB (Ethernet), snapshot length 262144
dropped privs to tcpdump
6 tipkali
2018-03-01 - 2022-11-01: Всероссийский государственный университет юстиции, Иркутск. Должность: Технический специалист. Дополнительная информация: Обязанности - работа с сайтами, поддержка функционирования серверов и сервисов СУБД, техническая поддержка пользователей. / Навыки - Python, Виртуализация, Linux, Windows. Достижения: В рамках "Импортозамещения с Windows на Linux" настроил виртуальный сервер с Linux системой и развернул в нем раздачу по сети установщика образа на компьютеры, на что сэкономило время на установку Linux в компьютерных классах.
Show
Цель:
# Установить и настроить Hyper-V в Windows Server 2012.
# Протестировать клиентский компьютер RedOS на работоспособность с ПО.
# Развернуть Pxe сервер для развертывания Redos с загрузкой в Uefi по сети.
# Установить и настроить web-сервер для публикации файлов дистрибутива РЕД ОС в локальной сети.
# Установить и настроить tftp.
# Создать файл kickstart, Записать файл на локальный или удаленный носитель, Создать загрузочный диск, с которого будет запускаться установка, Предоставить доступ к установочной структуре, Начать процесс установки.
# Установить и настроить РЕД ОС по PXE через VNC вместо Kickstart.
# Установить виртуальный сервер AltLinux в Hyper-V.
# Протестировать клиентский компьютер Alt Linux на работоспособность с ПО.
Skills:
# Администрирование локальных, виртуальных и облачных серверов.
Task:
Ввод компьютера в домен Windows и изменение имени хоста.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum install join-to-domain
[root@hvredos ~]# join-to-domain.sh
[root@hvredos ~]# reboot
[root@hvredos ~]# hostname hvredos.tdomain.ru
[root@hvredos ~]# hostname
hvredos.tdomain.ru
[root@hvredos ~]# vim /etc/hostname
[root@hvredos ~]# cat /etc/hostname
hvredos.tdomain.ru
[root@hvredos ~]# vim /etc/hosts
[root@hvredos ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
127.0.0.1 hvredos.tdomain.ru hvredos
Task:
Установка Консультант Плюс и подключение сетевых директорий с использованием automount и механизма Kerberos.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum install wine winetricks
# winetricks riched30 winhttp
- Wine - Wine Configuration - Графика - уберите галочку в пункте "Разрешить менеджеру окон декорировать окна". - Для запуска «Консультант Плюс» на рабочей станции подключите сетевой диск с «Консультантом» - Сделать это можно с помощью подключения сетевых директорий с использованием automount и механизма Kerberos
[root@hvredos ~]# smbclient -L hvredos -k
Sharename Type Comment
--------- ---- -------
Consultant Disk
ConsultantR Disk
S Disk
...
[root@hvredos ~]# yum install cifs-utils autofs
[root@hvredos ~]# klist
Ticket cache: FILE:/tmp/krb5cc_1
Default principal: tuser@tdomain.ru
Valid starting Expires service principal
31.08.2020 09:51:47 31.08.2020 19:51:47 krbtgt/tdomain.ru@tdomain.ru
renew until 31.08.2020 19:51:47
[root@hvredos ~]# vim /etc/auto.master
[root@hvredos ~]# cat /etc/auto.master
...
/mnt/.hpwindows /etc/auto.samba --ghost
[root@hvredos ~]# vim /etc/auto.samba
[root@hvredos ~]# cat /etc/auto.samba
Students -fstype=cifs,multiuser,cruid=tuser,sec=krb5,domain=tdomain.ru,vers=2.1 ://hpwindows/Students
Task:
Для подключения скрытых сетевых каталогов необходимо экранировать символ $.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
Consultant -fstype=cifs,multiuser,cruid=tuser,sec=krb5,domain=tdomain.ru,vers=2.1 ://hpwindows/Consultant\$
Task:
В файле /etc/krb5.conf нужно закомментировать строку (если она ещё не закомментирована).
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# cat /etc/krb5.conf
...
default_ccache_name = KEYRING:persistent:%{uid}
...
[root@hvredos ~]# vim/etc/krb5.conf
[root@hvredos ~]# cat /etc/krb5.conf
...
default_ccache_name = FILE:/tmp/krb5cc_%{uid}
...
[root@hvredos ~]# systemctl start autofs.service
[root@hvredos ~]# systemctl enable autofs --now
[root@hvredos ~]# ls /mnt/.hpwindows/Consultant/
... cons.exe ...
[root@hvredos ~]# winecfg
[root@hvredos ~]# wine /mnt/.hpwindows/Сonsultant/cons.exe /linux /yes
Task:
Создать ярлык Консультант +.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /home/tuser@tdomain.ru/Рабочий\ стол/Consultant.desktop
[root@hvredos ~]# cat /home/tuser@tdomain.ru/Рабочий\ стол/Consultant.desktop
[Desktop Entry]
Name=ConsultantPlus
Exec=wine /mnt/.hpwindows/Сonsultant/cons.exe
Type=Application
StartupNotify=true
Comment=ConsultantPlus
icon=43D4_Cons.0
StartupWMClass=c.exe
Task:
В случае замедленной работы можно добавить ключ /sprocess=0.
При нормальной работе, не добавляйте этот ключ.
Ключ /yes необходим для подавления сообщения об ошибке
[WNetGetUniversalName ...] : NO_NETWORK
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ln -s /mnt/.hvredos/S /home/tuser@tdomain.ru/Рабочий\ стол/S
Task:
Создание ярлыка для сетевой директории
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ln -s /mnt/.hvredos/S /home/tuser@tdomain.ru/Рабочий\ стол/S
Task:
Oграничение доступа к USB накопителям.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /etc/udev/rules.d/99-usb.rules
[root@hvredos ~]# cat /etc/udev/rules.d/99-usb.rules
ENV{ID_USB_DRIVER}=="usb-storage",ENV{UDISKS_IGNORE}="1"
# udevadm control --reload-rules
Task:
Запрет создания ярлыков и файлов на рабочем столе.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# tune2fs -l /dev/sda1 | grep "Default mount options:"
Default mount options: user_xattr acl
[root@hvredos ~]# vim /home/tuser@tdomain.ru/.config/user-dirs.dirs
[root@hvredos ~]# cat /home/tuser@tdomain.ru/.config/user-dirs.dirs
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="$HOME/Рабочий стол"
XDG_DOWNLOAD_DIR="$HOME/Загрузки"
XDG_TEMPLATES_DIR="$HOME/Шаблоны"
XDG_PUBLICSHARE_DIR="$HOME/Общедоступные"
XDG_DOCUMENTS_DIR="$HOME/Документы"
XDG_MUSIC_DIR="$HOME/Музыка"
XDG_PICTURES_DIR="$HOME/Изображения"
XDG_VIDEOS_DIR="$HOME/Видео"
[root@hvredos ~]# ls -la /home/tuser@tdomain.ru/.config
...
-rw-------. 1 tuser ïîëüçîâàòåëè äîìåíà 714 àâã 31 09:52 user-dirs.dirs
-rw-r--r--. 1 tuser ïîëüçîâàòåëè äîìåíà 5 àâã 31 09:52 user-dirs.locale
[root@hvredos ~]# chown root:root /home/tuser@tdomain.ru/.config/user-dirs.dirs
[root@hvredos ~]# ls -la /home/tuser@tdomain.ru/.config
...
-rw-------. 1 root root 714 àâã 31 09:52 user-dirs.dirs
-rw-r--r--. 1 tuser ïîëüçîâàòåëè äîìåíà 5 àâã 31 09:52 user-dirs.locale
[root@hvredos ~]# ls -la
...
drwxr-xr-x. 2 tuser ïîëüçîâàòåëè äîìåíà 4096 àâã 31 09:52 'Рабочий стол'
...
[root@hvredos ~]# chown -R root:root /home/tuser@tdomain.ru/Рабочий\ стол/
[root@hvredos ~]# ls -la
...
drwxr-xr-x. 2 root root 4096 àâã 31 09:52 'Рабочий стол'
drwxr-xr-x. 2 tuser ïîëüçîâàòåëè äîìåíà 4096 àâã 31 09:52 Øàáëîíû
Task:
Как скрыть пользователей от экрана входа.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# cat /var/lib/AccountsService/users/user
[User]
Language=
XSession=
SystemAccount=false
[root@hvredos ~]# vim /var/lib/AccountsService/users/user
[root@hvredos ~]# cat /var/lib/AccountsService/users/user
[User]
Language=
XSession=gnome
SystemAccount=true
Task:
Добавление пользователя из доменной сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /var/lib/AccountsService/users/tuser
[root@hvredos ~]# cat /var/lib/AccountsService/users/tuser
[User]
Language=
XSession=
Icon=/home/tuser@tdomain.ru/.face
SystemAccount=false
Task:
Отключить сетевые принтеры.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /etc/avahi/avahi-daemon.conf
cat /etc/avahi/avahi-daemon.conf
# This file is part of avahi.
...
enable-dbus=no
...
[root@hvredos ~]# reboot
Task:
Установка Gimp.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum provides gimp
[root@hvredos ~]# yum list | grep gimp
gimp.hvredos86 2:2.8.22-2.el7 base
[root@hvredos ~]# yum -y install gimp
Task:
Запись дисков Linux в терминале.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ls
13 'MyTestX Tests'
[root@hvredos ~]# pwd
/home/tuser@tdomain.ru/Documents
[root@hvredos ~]# mkisofs -o first.iso /home/tuser@tdomain.ru/Documents/13
[root@hvredos ~]# ls
13 first.iso 'MyTestX Tests'
[root@hvredos ~]# mkisofs -l -o first.iso /home/tuser@tdomain.ru/Documents/13
[root@hvredos ~]# ls
13 first.iso 'MyTestX Tests'
[root@hvredos ~]# mkisofs -D -l -o first.iso /home/tuser@tdomain.ru/Documents/13
[root@hvredos ~]# mkisofs -J -l -o first.iso /home/tuser@tdomain.ru/Documents/13
Warning: creating filesystem with Joliet extensions but without Rock Ridge
extensions. It is highly recommended to add Rock Ridge.
...
Joliet tree sort failed. The -joliet-long switch may help you.
[root@hvredos ~]# -joliet-long
[root@hvredos ~]# mkisofs -J -joliet-long -l -o first.iso /home/tuser@tdomain.ru/Documents/13
Warning: creating filesystem with Joliet extensions but without Rock Ridge
extensions. It is highly recommended to add Rock Ridge.
...
99.90% done, estimate finish Fri Jan 15 11:29:00 2021
Total translation table size: 0
Total rockridge attributes bytes: 0
Total directory bytes: 0
Path table size(bytes): 10
Task:
Если вы собираетесь записать образ на диск ubuntu DVD-RW/CD-RW необходимо сначала его очистить.
Здесь и ниже /dev/sr0 - адрес файла вашего привода.
first.iso - это файл образа, 16 - скорость записи, а опция -eject - заставляет извлечь диск из привода после записи.
Можно указывать скорость 4, 8 и 16. Желательно выбирать минимальную скорость, так диск запишется более качественно.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# cdrecord -dev=/dev/sr0 -v blank=fast
[root@hvredos ~]# cd Documents/
[root@hvredos ~]# ls
13 first.iso 'MyTestX Tests'
[root@hvredos ~]# cdrecord -dev=/dev/sr0 -speed=16 -eject -v first.iso
Task:
Установка клиента 1С.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum -y install webkitgtk3
[root@hvredos ~]# ls
1C_Enterprise83-client-8.3.14-17.x86_64.rpm
1C_Enterprise83-common-8.3.14-17.x86_64.rpm
1C_Enterprise83-server-8.3.14-17.x86_64.rpm
[root@hvredos ~]# yum -y install 1C_Enterprise83-*
Task:
Установка аналога paint.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum list | grep paint
kolourpaint.hvredos86 17.12.1-2.el7 base
kolourpaint.x86_64 17.12.1-2.el7 base
kolourpaint-libs.hvredos86 17.12.1-2.el7 base
kolourpaint-libs.x86_64 17.12.1-2.el7 base
[root@hvredos ~]# yum -y install kolourpaint
Task:
Настройка оповещения и автоматического обновления пакетов в РЕД ОС с помощью yum-cron.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# yum -y install yum-cron
[root@hvredos ~]# vim /etc/yum/yum-cron.conf
[root@hvredos ~]# cat /etc/yum/yum-cron.conf
[commands]
...
apply_updates = no
[root@hvredos ~]# vim /etc/yum/yum-cron.conf
[root@hvredos ~]# cat /etc/yum/yum-cron.conf
[commands]
...
apply_updates = yes
[root@hvredos ~]# systemctl enable yum-cron --now
Task:
Если не требуется обновлять определенные пакеты (как вручную, так и автоматически), то добавляем их в исключение.
Например, kernel и php.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# nano /etc/yum.conf
exclude=kernel, php
Task:
Если не требуется обновлять пакеты ТОЛЬКО в автоматическом режиме, тогда в /etc/yum/yum-cron.conf , в раздел [base], добавляем следующую строку:
# Администрирование локальных, виртуальных и облачных серверов.
Decision
[root@hvredos ~]# nano /etc/yum/yum-cron.conf
exclude=kernel* php*
Task:
Установка Anydesk.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# tee /etc/yum.repos.d/AnyDesk-RHEL.repo <<EOF
> [anydesk]
> name=AnyDesk RHEL - stable
> baseurl=http://rpm.anydesk.com/rhel/\$basearch/
> gpgcheck=1
> repo_gpgcheck=1
> gpgkey=https://keys.anydesk.com/repos/RPM-GPG-KEY
> EOF
[root@hvredos ~]# dnf makecache
[root@hvredos ~]# dnf install -y redhat-lsb-core
[root@hvredos ~]# dnf install anydesk
[root@hvredos ~]# rpm -qi anydesk
[root@hvredos ~]# systemctl status anydesk.service
[root@hvredos ~]# exit
[root@hvredos ~]# anydesk
Task:
Создание загрузочных носителей.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# fdisk -l
...
Диск /dev/sdb: 3,61 GiB, 3880452096 байт, 7579008 секторов
Disk model: Silicon-Power4G
Единицы: секторов по 1 * 512 = 512 байт
Размер сектора (логический/физический): 512 байт / 512 байт
Размер I/O (минимальный/оптимальный): 512 байт / 512 байт
Тип метки диска: gpt
Идентификатор диска: 2BAC44AA-F36D-461C-B12A-D251E7E9373F
Устр-во начало Конец Секторы Размер Тип
/dev/sdb1 2048 7578974 7576927 3,6G Microsoft basic data
[root@hvredos ~]# ls
Zorin-OS-16.1-Core-64-bit.iso
[root@hvredos ~]# dd if=Zorin-OS-16.1-Core-64-bit.iso of=/dev/sdb1 bs=8MB status=progress oflag=direct
3058237440 байт (3,1 GB, 2,8 GiB) скопирован, 659 s, 4,6 MB/s
382+1 записей получено
382+1 записей отправлено
3058237440 байт (3,1 GB, 2,8 GiB) скопирован, 659,39 s, 4,6 MB/s
Task:
Развернуть Pxe сервер для развертывания Redos с загрузкой в Uefi по сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# ifconfig
enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet tipwindowsserver netmask Mask1 broadcast IpAddr.255
...
[root@hvredos ~]# dnf install dhcp tftp-server syslinux httpd dnf-plugins-core -y
[root@hvredos ~]# mkdir /var/lib/tftpboot/pxelinux.cfg
[root@hvredos ~]# mkdir /var/lib/tftpboot/uefi
[root@hvredos ~]# mkdir -p /var/lib/tftpboot/images/REDOS
[root@hvredos ~]# cp /usr/share/syslinux/{chain.c32,mboot.c32,memdisk,menu.c32,pxelinux.0,ldlinux.c32,libutil.c32} /var/lib/tftpboot/
[root@hvredos ~]# chmod 777 /var/lib/tftpboot/pxelinux.0
[root@hvredos ~]# dnf download shim-x64 grub2-efi-x64 --downloaddir=/root/
[root@hvredos ~]# cd /root/
[root@hvredos ~]# rpm2cpio shim-x64-*.rpm | cpio -dimv
[root@hvredos ~]# rpm2cpio grub2-efi-x64-*.rpm | cpio -dimv
[root@hvredos ~]# cp ./boot/efi/EFI/BOOT/BOOTX64.EFI /var/lib/tftpboot/uefi
[root@hvredos ~]# cp ./boot/efi/EFI/redos/grubx64.efi /var/lib/tftpboot/uefi
[root@hvredos ~]# chmod 777 /var/lib/tftpboot/uefi/*.*
[root@hvredos ~]# umount -t iso9660 -o loop redos-MUROM-7.3.2-20211027.0-Everything-x86_64-DVD1.iso /mnt/
[root@hvredos ~]# cp -vR /mnt/* /var/lib/tftpboot/images/REDOS/
[root@hvredos ~]# umount /mnt/
[root@hvredos ~]# vim /etc/dhcp/dhcpd.conf
[root@hvredos ~]# cat /etc/dhcp/dhcpd.conf
non-authoritative;
allow bootp;
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
subnet tipwindowsserver.0 netmask Mask1 {
option routers tipwindowsserver;
range tipwindowsserver.70 tipwindowsserver.80;
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
next-server tipwindowsserver;
if option architecture-type = 00:07 {
filename "uefi/grubx64.efi";
}
else {
filename "pxelinux.0";
}
}
}
# systemctl enable dhcpd --now
[root@hvredos ~]# vim /var/lib/tftpboot/pxelinux.cfg/default
[root@hvredos ~]# cat /var/lib/tftpboot/pxelinux.cfg/default
default menu.c32
PROMPT 0
TIMEOUT 150
MENU TITLE PXE Menu
LABEL REDOS 7.3
MENU LABEL REDOS 7.3
KERNEL images/REDOS/images/pxeboot/vmlinuz
APPEND initrd=images/REDOS/images/pxeboot/initrd.img ramdisk_size=128000 ip=dhcp inst.repo=http://tipwindowsserver/images/REDOS/
[root@hvredos ~]# vim /var/lib/tftpboot/uefi/grub.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/uefi/grub.cfg
function load_video {
insmod efi_gop
insmod efi_uga
insmod video_bochs
insmod video_cirrus
insmod all_video
}
load_video
set gfxpayload=keep
insmod gzio
menuentry 'REDOS 7.3' {
linux images/REDOS/images/pxeboot/vmlinuz ip=dhcp inst.repo=http://tipwindowsserver/images/REDOS/
initrd images/REDOS/images/pxeboot/initrd.img
}
Task:
Используем web-сервер для публикации файлов дистрибутива РЕД ОС в локальной сети.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /etc/httpd/conf.d/pxeboot.conf
[root@hvredos ~]# cat /etc/httpd/conf.d/pxeboot.conf
Alias /images /var/lib/tftpboot/images
<Directory /var/lib/tftpboot/images>
Options Indexes FollowSymLinks
Require ip 127.0.0.1 tipwindowsserver.0/24
</Directory>
[root@hvredos ~]# systemctl enable httpd --now
Task:
Настройка и запуск службы tftp.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /usr/lib/systemd/system/tftp.service
[root@hvredos ~]# cat /usr/lib/systemd/system/tftp.service
...
[Install]:
WantedBy=multi-user.target
Also=tftp.socket
...
[root@hvredos ~]# vim /usr/lib/systemd/system/tftp.socket
[root@hvredos ~]# cat /usr/lib/systemd/system/tftp.socket
...
[Unit]
Description=Tftp Server Activation Socket
[Socket]
ListenDatagram=0.0.0.0:69
[Install]
WantedBy=sockets.target
...
[root@hvredos ~]# systemctl daemon-reload
[root@hvredos ~]# systemctl enable tftp --now
[root@hvredos ~]# ausearch -c 'httpd' --raw | audit2allow -M my-httpd
[root@hvredos ~]# semodule -i my-httpd.pp
Task:
Автоматизация развертывания (kickstart)
Создать файл kickstart, Записать файл на локальный или удаленный носитель, Создать загрузочный диск, с которого будет запускаться установка, Предоставить доступ к установочной структуре, Начать процесс установки.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# dnf install pykickstart -y
[root@hvredos ~]# cp /root/anaconda-ks.cfg /var/lib/tftpboot
[root@hvredos ~]# mv /var/lib/tftpboot/anaconda-ks.cfg /var/lib/tftpboot/ks.cfg
[root@hvredos ~]# vim /var/lib/tftpboot/pxelinux.cfg/default
[root@hvredos ~]# cat /var/lib/tftpboot/pxelinux.cfg/default
...
APPEND initrd=images/REDOS/images/pxeboot/initrd.img ramdisk_size=128000 ip=dhcp method=http://tipwindowsserver/images/REDOS/ devfs=nomount inst.ks=http://tipwindowsserver/ks.cfg
[root@hvredos ~]# vim /var/lib/tftpboot/uefi/grub.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/uefi/grub.cfg
... {
linux images/REDOS/images/pxeboot/vmlinuz ip=dhcp kernel vmlinuz inst.repo=http://tipwindowsserver/images/REDOS/ inst.ks=http://tipwindowsserver/ks.cfg
initrd images/REDOS/images/pxeboot/initrd.img
}
[root@hvredos ~]# vim /var/lib/tftpboot/ks.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/ks.cfg
# Здесь указываем раскладку клавиатуры
keyboard --vckeymap=us --xlayouts='us','ru' --switch='grp:alt_shift_toggle'
# Системная локаль
lang ru_RU.UTF-8
# Информация о сетевом интерфейсе и имя машины
network --bootproto=dhcp --device=enp2s0 --noipv6 --activate
network --hostname=hostname1337
# Пароль Root представлен в виде хэш-суммы
rootpw --iscrypted $6$DUu0yyOYMRbGS8gL$9zHYPsxROGEZdDKG0wnf7h8SGnKOp3V272De6oGTVUsz2uBLmEeiR6T6cInRN5dyWcxNXh5fVluEUTQ/3rmzB0
# Настройка сервисов (в данном случае сервис по обновлению меток времени и дат)
services --enabled="chronyd"
# Настройка временной зоны
timezone Europe/Moscow --isUtc
#Настройка локального пользователя
user --groups=wheel --name=mekka --password=$6$83fyYZ7KMS7G9t6A$E5/99/ffOwjUOo8THr1ngqGDdKMimpTZf3IT9S/SI98BTV7dta7GksLYnQEZjtqqyZQrwibSRlvYccRqHB7m8/ --iscrypted --gecos="Mekka"
# Настройка xorg при загрузке
xconfig --startxonboot
# Указание загрузочного сектора и тип структуры
bootloader --location=mbr --boot-drive=sda
# Удаление всей информации с партиций для последующей установки
clearpart --none --initlabel
# Здесь указана вся разметка диска
part /boot --fstype="xfs" --onpart=sda2
part biosboot --fstype="biosboot" --noformat --onpart=sda4
part pv.31 --fstype="lvmpv" --noformat --onpart=sda3
part /boot/efi --fstype="efi" --onpart=sda1 --fsoptions="umask=0077,shortname=winnt"
volgroup ro --noformat --useexisting
logvol swap --fstype="swap" --useexisting --name=swap --vgname=ro
logvol /home --fstype="xfs" --noformat --useexisting --name=home --vgname=ro
logvol / --fstype="ext4" --useexisting --name=root --vgname=ro
#дополнительные пакеты для установки
%packages
@^mate-desktop-environment
@backup-client
@base
@branding
@core
@desktop-debugging
@dial-up
@directory-client
@fonts
@guest-agents
@guest-desktop-agents
@input-methods
@internet-applications
@internet-browser
@java-platform
@mate-desktop
@multimedia
@network-file-system-client
@print-client
@x11
chrony
%end
#настройка аварийных дампов памяти в случае сбоев (оставить как есть)
%addon com_redhat_kdump --enable --reserve-mb='auto'
%end
#настройка анаконды (оставить как есть)
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end
Task:
Настройка установки РЕД ОС по PXE через VNC вместо Kickstart.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
[root@hvredos ~]# vim /var/lib/tftpboot/pxelinux.cfg/default
[root@hvredos ~]# cat /var/lib/tftpboot/pxelinux.cfg/default
...
APPEND initrd=images/REDOS/images/pxeboot/initrd.img ramdisk_size=128000 ip=dhcp method=http://tipwindowsserver/images/REDOS/ devfs=nomount inst.vnc inst.vncpassword=tpassword
[root@hvredos ~]# vim /var/lib/tftpboot/uefi/grub.cfg
[root@hvredos ~]# cat /var/lib/tftpboot/uefi/grub.cfg
... {
linux images/REDOS/images/pxeboot/vmlinuz ip=dhcp kernel vmlinuz inst.repo=http://tipwindowsserver/images/REDOS/ inst.vnc inst.vncpassword=tpassword
initrd images/REDOS/images/pxeboot/initrd.img
}
Source:
# https://redos.red-soft.ru/base/other-soft/other-other/consultant/?sphrase_id=53349
# https://wtuseri.astralinutdomain.ru/pages/viewpage.action?pageId=61574227
# https://askubuntu.ru/questions/21203/kak-skry-t-pol-zovatelej-ot-e-krana-vxoda-v-gdm
# https://computingforgeeks.com/how-to-install-gimp-on-centos-rhel-8-desktop/
# https://ru.wtuserihow.com/%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C-ISO-%D1%84%D0%B0%D0%B9%D0%BB-%D0%B2-Linux
# https://losst.ru/zapis-diskov-v-ubuntu
# https://losst.ru/luchshie-analogi-paint-dlya-linux
# https://computingforgeeks.com/how-to-install-anydesk-on-centos-rhel-8/
Task:
Set up an ActiveDirectory/Login.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# apt-get install task-auth-ad-sssd
root@hvalt:~# net time set -S tdomain.ru
# system-auth write ad tdomain.ru hvalt hvalt 'tuser' 'tpassword'
Using short domain name -- hvalt
Joined 'hvalt' to dns domain 'tdomain.ru'
Successfully registered hostname with DNS
failed to call wbcGetDisplayName: WBC_ERR_WINBIND_NOT_AVAILABLE
Could not lookup sid S-1-5-21-965402400-3010625364-1855727791-513
failed to call wbcGetDisplayName: WBC_ERR_WINBIND_NOT_AVAILABLE
Could not lookup sid S-1-5-21-965402400-3010625364-1855727791-512
root@hvalt:~# wbinfo -t
checking the trust secret for domain hvredos via RPC calls succeeded
root@hvalt:~# acc
2 keyboards found
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platfohvredos plugin "xcb" in "" even though it was found.
This applhvredosation failed to start because no Qt platfohvredos plugin could be initialized. Reinstalling the applhvredosation may fix this problem.
Available platfohvredos plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vnc, xcb.
root@hvalt:~# exit
root@hvalt:~# exit
[root@hvredos ~]# ssh -X tuser@hvalt
root@hvalt:~# su -
root@hvalt:~# acc
2 keyboards found
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/.private/root/runtime-root'
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
WARNING: (alterator lookout evaluation): imported module (alterator presentation events) overrides core binding `when'
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
libpng warning: hvredosCP: known incorrect sRGB profile
root@hvalt:~# vim /etc/net/ifaces/eth0/resolv.conf
root@hvalt:~# cat /etc/net/ifaces/eth0/resolv.conf
nameserver tipwindowsserver
root@hvalt:~# hostnamectl set-hostname hvalt.tdomain.ru
root@hvalt:~# cat /etc/resolv.conf
search tdomain.ru
nameserver 127.0.0.1
root@hvalt:~# vim /etc/resolv.conf
root@hvalt:~# cat /etc/resolv.conf
# Configuration for resolv(8)
# See resolv.conf(5) for details
resolv_conf_head='# Do not edit manually, use\n# /etc/net/ifaces/<interface>/resolv.conf instead.'
resolv_conf=/etc/resolv.conf
# These interfaces will always be processed first.
interface_order='lo lo[0-9]* lo.*'
# These interfaces will be processed next, unless they have a metrhvredos.
dynamhvredos_order='tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*'
#Configuration files for named subscriber.
named_zones=/var/lib/bind/etc/resolv-zones.conf
named_options=/var/lib/bind/etc/resolv-options.conf
#Configuration files for dnsmasq subscriber.
dnsmasq_conf=/etc/dnsmasq.conf.d/60-resolv
dnsmasq_resolv=/etc/resolv.conf.dnsmasq
name_servers=127.0.0.1
root@hvalt:~# vim /etc/resolv.conf
root@hvalt:~# cat /etc/resolv.conf
# Configuration for resolv(8)
# See resolv.conf(5) for details
resolv_conf_head='# Do not edit manually, use\n# /etc/net/ifaces/<interface>/resolv.conf instead.'
resolv_conf=/etc/resolv.conf
# These interfaces will always be processed first.
#interface_order='lo lo[0-9]* lo.*'
interface_order='lo lo[0-9]* lo.* eth0'
search_domains=tdomain.ru
# These interfaces will be processed next, unless they have a metrhvredos.
dynamhvredos_order='tap[0-9]* tun[0-9]* vpn vpn[0-9]* wg[0-9]* ppp[0-9]* ippp[0-9]*'
#Configuration files for named subscriber.
named_zones=/var/lib/bind/etc/resolv-zones.conf
named_options=/var/lib/bind/etc/resolv-options.conf
#Configuration files for dnsmasq subscriber.
dnsmasq_conf=/etc/dnsmasq.conf.d/60-resolv
dnsmasq_resolv=/etc/resolv.conf.dnsmasq
#name_servers=127.0.0.1
root@hvalt:~# resolv -u
root@hvalt:~# cat /etc/resolv.conf
# Generated by resolv
# Do not edit manually, use
# /etc/net/ifaces/<interface>/resolv.conf instead.
search tdomain.ru
nameserver tipwindowsserver
...
nameserver 8.8.8.8
root@hvalt:~# hostname
hvalt.tdomain.ru
root@hvalt:~# dig _kerberos._udp.tdomain.ru SRV | grep ^_kerberos
_kerberos._udp.tdomain.ru. 600 IN SRV 0 100 88 M-1.tdomain.ru.
...
_kerberos._udp.tdomain.ru. 600 IN SRV 0 100 88 T-1.tdomain.ru.
root@hvalt:~# dig _kerberos._tcp.tdomain.ru SRV | grep ^_kerberos
_kerberos._tcp.tdomain.ru. 600 IN SRV 0 100 88 M-c.tdomain.ru.
...
_kerberos._tcp.tdomain.ru. 600 IN SRV 0 100 88 T-1.tdomain.ru.
root@hvalt:~# cat /etc/krb5.conf | grep default_realm
default_realm = tdomain.ru
# default_realm = EXAMPLE.COM
root@hvalt:~# cat /etc/krb5.conf | grep dns_lookup_realm
dns_lookup_realm = false
root@hvalt:~# cat /etc/krb5.conf | grep dns_lookup_kdc
dns_lookup_kdc = true
root@hvalt:~# exit
[root@hvredos ~]# ssh -X tuser@hvalt
root@hvalt:~# kinit tuser
root@hvalt:~# klist
Thvredosket cache: KEYRING:persistent:0:krb_ccache_CCjHpN1
Default principal: a-r@tdomain.ru
Valid starting Expires Servhvredose principal
10.08.2022 12:30:34 10.08.2022 22:30:34 krbtgt/tdomain.ru@tdomain.ru
renew until 17.08.2022 12:30:32
root@hvalt:~# apt-get install samba-client
root@hvalt:~# cat /etc/samba/smb.conf | grep realm
realm = tdomain.ru
root@hvalt:~# cat /etc/samba/smb.conf | grep workgroup
workgroup = hvalt
root@hvalt:~# cat /etc/samba/smb.conf | grep netbios
netbios name = hvalt
root@hvalt:~# cat /etc/samba/smb.conf | grep security
security = ads
root@hvalt:~# cat /etc/samba/smb.conf | grep method
kerberos method = system keytab
root@hvalt:~# cat /etc/samba/smb.conf | grep idmap
idmap config * : range = 200000-2000200000
idmap config * : backend = sss
root@hvalt:~# vim /etc/samba/smb.conf
root@hvalt:~# cat /etc/samba/smb.conf | grep idmap
idmap config * : range = 200000-2000200000
; idmap config * : backend = sss
idmap config * : backend = tdb
# testpahvalt
Load smb config files from /etc/samba/smb.conf
Loaded servhvredoses file OK.
Weak crypto is allowed
Server role: ROLE_DOMAIN_MEMBER
Press enter to see a dump of your servhvredose definitions
# Global parameters
[global]
kerberos method = system keytab
machine password timeout = 0
realm = tdomain.ru
security = ADS
template homedir = /home/tdomain.ru/%U
template shell = /bin/bash
winbind use default domain = Yes
workgroup = hvredos
idmap config * : range = 200000-2000200000
idmap config * : backend = tdb
[share]
comment = Commonplace
path = /srv/share
read only = No
[homes]
browseable = No
comment = Home Directory for '%u'
read only = No
root@hvalt:~# cat /etc/hosts
127.0.0.1 localhost.localdomain localhost
root@hvalt:~# vim /etc/hosts
root@hvalt:~# cat /etc/hosts
127.0.0.1 hvalt.tdomain.ru hvalt
root@hvalt:~# net ads join -U tuser
Enter tuser's password:
Using short domain name -- hvalt
Joined 'hvalt' to dns domain 'tdomain.ru'
root@hvalt:~# klist -k -e
Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
9 host/hvalt.tdomain.ru@tdomain.ru (aes256-cts-hmac-sha1-96)
9 host/hvalt@tdomain.ru (aes256-cts-hmac-sha1-96)
9 host/hvalt.tdomain.ru@tdomain.ru (aes128-cts-hmac-sha1-96)
9 host/hvalt@tdomain.ru (aes128-cts-hmac-sha1-96)
9 host/hvalt.tdomain.ru@tdomain.ru (DEPRECATED:arcfour-hmac)
9 host/hvalt@tdomain.ru (DEPRECATED:arcfour-hmac)
9 hvalt$@tdomain.ru (aes256-cts-hmac-sha1-96)
9 hvalt$@tdomain.ru (aes128-cts-hmac-sha1-96)
9 hvalt$@tdomain.ru (DEPRECATED:arcfour-hmac)
8 host/hvalt.tdomain.ru@tdomain.ru (aes256-cts-hmac-sha1-96)
8 host/hvalt@tdomain.ru (aes256-cts-hmac-sha1-96)
8 host/hvalt.tdomain.ru@tdomain.ru (aes128-cts-hmac-sha1-96)
8 host/hvalt@tdomain.ru (aes128-cts-hmac-sha1-96)
8 host/hvalt.tdomain.ru@tdomain.ru (DEPRECATED:arcfour-hmac)
8 host/hvalt@tdomain.ru (DEPRECATED:arcfour-hmac)
8 hvalt$@tdomain.ru (aes256-cts-hmac-sha1-96)
8 hvalt$@tdomain.ru (aes128-cts-hmac-sha1-96)
8 hvalt$@tdomain.ru (DEPRECATED:arcfour-hmac)
root@hvalt:~# apt-get install sssd-ad
root@hvalt:~# cat /etc/sssd/sssd.conf
[sssd]
config_file_version = 2
servhvredoses = nss, pam
# Managed by system facility command:
## control sssd-drop-privileges unprivileged|privileged|default
user = _sssd
# SSSD will not start if you do not configure any domains.
domains = tdomain.ru
[nss]
[pam]
[domain/tdomain.ru]
id_provider = ad
auth_provider = ad
chpass_provider = ad
access_provider = ad
default_shell = /bin/bash
fallback_homedir = /home/%d/%u
debug_level = 0
; cache_credentials = false
ad_gpo_ignore_unreadable = true
ad_gpo_access_control = pehvredosissive
ad_update_samba_machine_account_password = true
root@hvalt:~# vim /etc/sssd/sssd.conf
root@hvalt:~# cat /etc/sssd/sssd.conf
[sssd]
config_file_version = 2
servhvredoses = nss, pam
# Managed by system facility command:
## control sssd-drop-privileges unprivileged|privileged|default
#user = _sssd
user=root
# SSSD will not start if you do not configure any domains.
domains = tdomain.ru
[nss]
[pam]
[domain/tdomain.ru]
id_provider = ad
auth_provider = ad
chpass_provider = ad
access_provider = ad
;ldap_id_mapping = False
default_shell = /bin/bash
fallback_homedir = /home/%d/%u
debug_level = 0
;use_fully_qualified_names = True
; cache_credentials = True
ad_gpo_ignore_unreadable = true
ad_gpo_access_control = pehvredosissive
ad_update_samba_machine_account_password = true
root@hvalt:~# grep sss /etc/nsswitch.conf
passwd: files sss
shadow: tcb files sss
group: files [SUCCESS=merge] sss role
root@hvalt:~# control system-auth sss
root@hvalt:~# servhvredose sssd status
active
root@hvalt:~# servhvredose sssd start
root@hvalt:~# getent passwd tuser
tuser:*:1-9:1-3:в-н:/home/tdomain.ru/tuser:/bin/bash
root@hvalt:~# id tuser
uid=1-9(tuser) gid=1-3(пользователи домена) группы=1-3(пользователи домена),1-8(администраторы dhcp),1-0(tuser),11-0(пользователи филиалы),1-1(tuser),1-9(i-n)
root@hvalt:~# net ads info
LDAP server: IpAddr2
LDAP server name: MOW-1.tdomain.ru
Realm: tdomain.ru
Bind Path: dc=hvalt,dc=RU
LDAP port: 389
Server time: Ср, 10 авг 2022 13:08:06 +08
KDC server: IpAddr2
Server time offset: 0
Last machine account password change: Ср, 10 авг 2022 12:50:51 +08
root@hvalt:~# net ads testjoin
Join is OK
root@hvalt:~# cat /etc/lightdm/lightdm.conf | grep greeter-hide-
# greeter-hide-users = True to hide the user list
#greeter-hide-users=false
greeter-hide-users = true
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep show-language
show-language-selector = false
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep show-indhvredosators
root@hvalt:~# vim /etc/lightdm/lightdm-gtk-greeter.conf
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep show-indhvredosators
show-indhvredosators=~a11y;~power;~session;~language
root@hvalt:~# vim /etc/lightdm/lightdm-gtk-greeter.conf
root@hvalt:~# cat /etc/lightdm/lightdm-gtk-greeter.conf | grep enter-
enter-username = true
root@hvalt:~# reboot
Task:
Учетные записи и групп в Альт Линукс.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# apt-get install libnss-role
root@hvalt:~# groupadd -r localadmins
groupadd: группа «localadmins» уже существует
root@hvalt:~# groupadd -r remote
groupadd: группа «remote» уже существует
root@hvalt:~# control sshd-allow-groups enabled
root@hvalt:~# sed -i 's/AllowGroups.*/AllowGroups = remote/' /etc/openssh/sshd_config
root@hvalt:~# roleadd users cdwriter cdrom audio proc radio camera floppy xgrp scanner uucp fuse
root@hvalt:~# roleadd localadmins wheel remote vboxusers
root@hvalt:~# roleadd 'Domain Users' users
Error 156: No such group
root@hvalt:~# roleadd 'Пользователи домена' users
root@hvalt:~# roleadd 'Администраторы домена' localadmins
root@hvalt:~# rolelst
users:cdwriter,cdrom,audio,proc,radio,camera,floppy,xgrp,scanner,uucp,fuse,video,vboxusers,vboxadd
localadmins:wheel,remote,vboxusers,vboxadd
пользователи домена:users
администраторы домена:localadmins
powerusers:remote,vboxadd,vboxusers
vboxadd:vboxsf
root@hvalt:~# id tuser
uid=1-9(tuser) gid=1-3(пользователи домена) группы=1-3(пользователи домена),11-0(пользователи филиалы),1-9(i-n),1-0(tuser),1-1(tuser),1-8(администраторы dhcp),100(users),80(cdwriter),22(cdrom),81(audio),19(proc),83(radio),440(camera),71(floppy),466(xgrp),467(scanner),14(uucp),483(fuse),488(video),481(vboxusers),455(vboxadd),454(vboxsf)
root@hvalt:~# roleadd 'tuser' localadmins
root@hvalt:~# roleadd 'tuser' wheel
root@hvalt:~# rolelst
users:cdwriter,cdrom,audio,proc,radio,camera,floppy,xgrp,scanner,uucp,fuse,video,vboxusers,vboxadd
localadmins:wheel,remote,vboxusers,vboxadd
пользователи домена:users
администраторы домена:localadmins
tuser:localadmins
powerusers:remote,vboxadd,vboxusers
vboxadd:vboxsf
root@hvalt:~# exit
[root@hvredos ~]# ssh -X tuser@hvalt
root@hvalt:~# id tuser
uid=1-9(tuser) gid=1-3(пользователи домена) группы=1-3(пользователи домена),11-0(пользователи филиалы),1-9(i-n),1-0(tuser),1-1(tuser),1-8(администраторы dhcp),100(users),80(cdwriter),22(cdrom),81(audio),19(proc),83(radio),440(camera),71(floppy),466(xgrp),467(scanner),14(uucp),483(fuse),488(video),481(vboxusers),455(vboxadd),454(vboxsf),101(localadmins),10(wheel),110(remote)
Task:
Добавить сетевые папки.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# apt-get install autofs
root@hvalt:~# vim /etc/auto.master
root@hvalt:~# cat /etc/auto.master
# Fohvredosat of this file:
# mountpoint map options
# For details of the fohvredosat look at autofs(8).
/mnt/auto /etc/auto.tab -t 5
/mnt/net /etc/auto.avahi -t 120
/mnt/.tdirectory /etc/auto.samba --ghost
root@hvalt:~# vim /etc/auto.samba
root@hvalt:~# cat /etc/auto.samba
s -fstype=cifs,multiuser,cruid=$USER,sec=krb5,domain=tdomain.ru,vers=1.0 ://hvalt/tdirectory1
o -fstype=cifs,multiuser,cruid=$USER,sec=krb5,domain=tdomain.ru,vers=1.0 ://hvalt/tdirectory2
root@hvalt:~# systemctl enable autofs
root@hvalt:~# systemctl start autofs
root@hvalt:~# ls -la /mnt/.tdirectory/
drwxr-xr-x 4 root root 0 авг 11 14:09 .
drwxr-xr-x 5 root root 4096 авг 11 14:09 ..
d????????? ? ? ? ? ? o
d????????? ? ? ? ? ? s
Task:
Creating a Network Bridge interface.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@hvalt:~# mkdir /etc/net/ifaces/tethernet1
root@hvalt:~# cp /etc/net/ifaces/tethernet/* /etc/net/ifaces/tethernet1
root@hvalt:~# rm -f /etc/net/ifaces/tethernet/{i,r}*
root@hvalt:~# ls /etc/net/ifaces/tethernet1/
ipv4address options resolv.conf
root@hvalt:~# cat /etc/net/ifaces/tethernet1/options
BOOTPROTO=dhcp
TYPE=eth
NM_CONTROLLED=yes
DISABLED=yes
CONFIG_WIRELESS=no
SYSTEMD_BOOTPROTO=dhcp4
CONFIG_IPV4=yes
SYSTEMD_CONTROLLED=no
root@hvalt:~# vim /etc/net/ifaces/tethernet1/options
root@hvalt:~# ls /etc/net/ifaces/
default tethernet lo unknown tethernet1
root@hvalt:~# vim /etc/net/ifaces/tethernet1/options
root@hvalt:~# cat /etc/net/ifaces/tethernet1/options
BOOTPROTO=static
CONFIG_WIRELESS=no
CONFIG_IPV4=yes
HOST='tethernet'
ONBOOT=yes
TYPE=bri
root@hvalt:~# ls /etc/net/ifaces/tethernet/
ipv4address options resolv.conf
root@hvalt:~# service network restart
Source:
# https://www.altlinux.org/ActiveDirectory/Login#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BE%D0%B2
# https://www.altlinux.org/SSSD/AD
# https://www.altlinux.org/%D0%A1%D0%B5%D1%82%D0%B5%D0%B2%D0%BE%D0%B9_%D0%BC%D0%BE%D1%81%D1%82
# https://www.altlinux.org/PostgreSQL#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B8_%D0%BD%D0%B0%D1%87%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%B7%D0%B0%D0%BF%D1%83%D1%81%D0%BA
# https://www.tecmint.com/install-postgresql-and-pgadmin-in-ubuntu/
# https://o7planning.org/11353/install-pgadmin-on-ubuntu
# https://redos.red-soft.ru/base/server-configuring/dbms/install-postgresql/?sphrase_id=53348
# https://redos.red-soft.ru/base/server-configuring/dbms/pgadmin4/
2022-11-01 - 2023-05-30: Сбер Университет, Иркутск. Должность: Инженер с большими данными - Дополнительное образование. Дополнительная информация: Навыки: Виртуализация, Linux, Sql, Python, English, Clouds, Bash, Etl-процессы, DWH, AntiFraud. Достижения: При работе с транзакционными банковскими данными с помощью Python и SQL разработал хранилище данных - DWH, процесс сбора, очистки, трансформации и хранения данных, систему автоматического поиска мошеннических операций (AntiFraud-система).
Show
# Администрирование локальных, виртуальных и облачных серверов.
# Разработка Хранилище данных.
Task:
Администрирование локальных, виртуальных и облачных серверов. Docker установка
Decision:
$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt update
$ apt-cache policy docker-ce
$ sudo apt install docker-ce
$ systemctl status docker
$ sudo docker run hello-world
$ sudo usermod -aG docker tuser
$ docker run hello-world
$ curl -SL https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
$ chmod +x ~/.docker/cli-plugins/docker-compose
$ docker compose version
Task:
Администрирование локальных, виртуальных и облачных серверов. Create Postgres Docker Image
Decision:
$ mkdir docks
$ mkdir docks/postgres
$ touch docks/postgres/Dockerfile
$ vim docks/postgres/Dockerfile
$ cat docks/postgres/Dockerfile
FROM postgres:latest
ENV POSTGRES_USER YOUR-USERNAME
ENV POSTGRES_PASSWORD YOUR-PASSWORD
ENV POSTGRES_DB YOUR-DB
EXPOSE 5432
$ cd docks/postgres/
$ docker build -t YOUR-DB/postgres .
$ docker run -d --name postg -p 5432:5432 YOUR-DB/postgres
FULLYOUR-ID1
$ sudo netstat -tulpn | grep 5432
$ docker logs -f postg
$ docker ps
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
YOUR-ID1 YOUR-DB/postgres "docker-entrypoint.s…" 10 hours ago Exited (255) 5 minutes ago 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp postg
$ docker start postg
Task:
Администрирование локальных, виртуальных и облачных серверов. Pgadmin Postgres Docker container
Decision:
$ google-chrome https://www.pgadmin.org/download/ &
$ curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo gpg --dearmor -o /usr/share/keyrings/packages-pgadmin-org.gpg
$ sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/packages-pgadmin-org.gpg] https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list && apt update'
$ sudo apt install pgadmin4
$ sudo /usr/pgadmin4/bin/setup-web.sh
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
YOUR-ID YOUR-DB/postgres "docker-entrypoint.s…" 45 hours ago Exited (255) 37 seconds ago 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp postg
$ docker start postg
$ google-chrome http://YOUR-IP/pgadmin4 &
Task:
Администрирование локальных, виртуальных и облачных серверов. Stop, remove postgres container
Decision:
$ docker stop postg
$ docker rm postg
$ docker push YOUR-DB/postgres
Task:
Разработка Хранилище данных. Разработать ETL процесс, получающий ежедневную выгрузку данных (предоставляется за 3 дня), загружающий ее в хранилище данных и ежедневно строящий отчет.
Ежедневно некие информационные системы выгружают три следующих файла:
1. Список транзакций за текущий день. Формат – CSV.
2. Список терминалов полным срезом. Формат – XLSX.
3. Список паспортов, включенных в «черный список» - с накоплением с начала месяца. Формат – XLSX.
Сведения о картах, счетах и клиентах хранятся в СУБД PostgreSQL. Во вложении реквизиты для подключения.
Вам предоставляется выгрузка за последние три дня, ее надо обработать. В качестве хранилища выступает ваша учебная база (edu). Данные должны быть загружены в хранилище со следующей структурой (имена сущностей указаны по существу, без особенностей правил нейминга, указанных далее).
Типы данных в полях можно изменять на однородные если для этого есть необходимость. Имена полей менять нельзя. Ко всем таблицам SCD1 должны
быть добавлены технические поля create_dt, update_dt; ко всем таблицам SCD2 должны быть добавлены технические поля effective_from, effective_to, deleted_flg.
По результатам загрузки ежедневно необходимо строить витрину отчетности по мошенническим операциям. Витрина строится накоплением, каждый новый отчет укладывается в эту же таблицу с новым report_dt.
В витрине должны содержаться следующие поля:
1. event_dt - Время наступления события. Если событие наступило по результату нескольких действий – указывается время действия, по которому установлен факт мошенничества.
2. passport - Номер паспорта клиента, совершившего мошенническую операцию.
3. fio - ФИО клиента, совершившего мошенническую операцию.
4. phone - Номер телефона клиента, совершившего мошенническую операцию.
5. event_type - Описание типа мошенничества.
6. report_dt - Время построения отчета.
Признаки мошеннических операций:
1. Совершение операции при просроченном или заблокированном паспорте.
2. Совершение операции при недействующем договоре.
3. Совершение операций в разных городах в течение одного часа.
4. Попытка подбора суммы. В течение 20 минут проходит более 3х операций соследующим шаблоном – каждая последующая меньше предыдущей, при этом отклонены все кроме последней. Последняя операция (успешная) в такой цепочке считается мошеннической.
При именовании таблиц необходимо придерживаться следующих правил (для автоматизации проверки):
1. YOUR-USERNAME.<CODE>_STG_<TABLE_NAME> - Таблицы для размещения стейджинговых таблиц (первоначальная загрузка), промежуточное выделение инкремента, если требуется. Временные таблицы, если такие потребуются в расчете, можно также складывать с таким именованием. Имя таблиц можете выбирать произвольное, но смысловое.
2. YOUR-USERNAME.<CODE>_DWH_FACT_<TABLE_NAME> - Таблицы фактов, загруженных в хранилище. В качестве фактов выступают сами транзакции и «черный список» паспортов. Имя таблиц – как в ER диаграмме.
3. YOUR-USERNAME.<CODE>_DWH_DIM_<TABLE_NAME> - Таблицы измерений, хранящиеся в формате SCD1. Имя таблиц – как в ER диаграмме.
4. YOUR-USERNAME.<CODE>_DWH_DIM_<TABLE_NAME>_HIST - Таблицы измерений, хранящиеся в SCD2 формате (только для тех, кто выполняет усложненное задание). Имя таблиц – как в ER диаграмме.
5. YOUR-USERNAME.<CODE>_REP_FRAUD - Таблица с отчетом.
6. YOUR-USERNAME.<CODE>_META_<TABLE_NAME> - Таблицы для хранения метаданных. Имя таблиц можете выбирать произвольное, но
смысловое.
7. <CODE> - 4 буквы вашего персонального кода.
Выгружаемые файлы именуются согласно следующему шаблону:
1. transactions_DDMMYYYY.txt
2. passport_blacklist_DDMMYYYY.xlsx
3. terminals_DDMMYYYY.xlsx
Предполагается что в один день приходит по одному такому файлу. После загрузки соответствующего файла он должен быть переименован в файл с расширением .backup чтобы при следующем запуске файл не искался и перемещен в каталог archive:
1. transactions_DDMMYYYY.txt.backup
2. passport_blacklist_DDMMYYYY.xlsx.back
3. upterminals_DDMMYYYY.xlsx.backup
Желающие могут придумать, обосновать и реализовать более технологичные и учитывающие сбои способы обработки (за это будет повышен балл).
В classroom выкладывается zip-архив, содержащий следующие файлы и каталоги:
1. main.py - Файл, обязательный. Основной процесс обработки.
2. файлы с данными - Файл, обязательный. Те файлы, которые вы получили в качестве задания. Просто скопируйте все 9 файлов.
3. main.ddl - Файл, обязательный. Файл с SQL кодом для создания всех необходимых объектов в базе edu.
4. main.cron - Файл, обязательный. Файл для постановки вашего процесса на расписание, в формате crontab
5. archive - Каталог, обязательный. Пустой, сюда должны перемещаться отработанные файлы
6. sql_scripts - Каталог, необязательный. Если вы включаете в main.py какие-то SQL скрипты, вынесенные в отдельные файлы – помещайте их сюда.
7. py_scripts - Каталог, необязательный. Если вы включаете в main.py какие-то python скрипты, вынесенные в отдельные файлы – помещайте их сюда.
Decision:
$ mkdir archive
$ touch main.py
$ chmod +x main.py
$ touch main.ddl
$ touch main.cron
$ cat snippet_pg.py
import psycopg2
import pandas as pd
# Создание подключения к PostgreSQL
conn = psycopg2.connect (
database = "YOUR-DB",
host = "YOUR-IP",
user = "YOUR-USERNAME",
password = "YOUR-PASSWORD",
port = "5432"
)
# Отключение автокоммита
conn.autocommit = False
# Создание курсора
cursor = conn.cursor()
####################################################
# Выполнение SQL кода в базе данных без возврата результата
cursor.execute( "INSERT INTO YOUR-USERNAME.testtable( id, val ) VALUES ( 1, 'ABC' )" )
conn.commit()
# Выполнение SQL кода в базе данных с возвратом результата
cursor.execute( "SELECT * FROM YOUR-USERNAME.testtable" )
records = cursor.fetchall()
for row in records:
print( row )
####################################################
# Формирование DataFrame
names = [ x[0] for x in cursor.description ]
df = pd.DataFrame( records, columns = names )
# Запись в файл
df.to_excel( 'pandas_out.xlsx', sheet_name='sheet1', header=True, index=False )
####################################################
# Чтение из файла
df = pd.read_excel( 'pandas.xlsx', sheet_name='sheet1', header=0, index_col=None )
# Запись DataFrame в таблицу базы данных
cursor.executemany( "INSERT INTO YOUR-USERNAME.testtable( id, val ) VALUES( %s, %s )", df.values.tolist() )
# Закрываем соединение
cursor.close()
conn.close()
$ /SCD1_incremental_full_script.sql
----------------------------------------------------------------------------
-- Подготока данных
create table YOUR-USERNAME.XXXX_source(
id integer,
val varchar(50),
update_dt timestamp(0)
);
insert into YOUR-USERNAME.XXXX_source ( id, val, update_dt ) values ( 1, 'A', now() );
insert into YOUR-USERNAME.XXXX_source ( id, val, update_dt ) values ( 2, 'B', now() );
insert into YOUR-USERNAME.XXXX_source ( id, val, update_dt ) values ( 3, 'C', now() );
update YOUR-USERNAME.XXXX_source set val = 'X', update_dt = now() where id = 3;
delete from YOUR-USERNAME.XXXX_source where id = 3;
create table YOUR-USERNAME.XXXX_stg(
id integer,
val varchar(50),
update_dt timestamp(0)
);
create table YOUR-USERNAME.XXXX_target (
id integer,
val varchar(50),
create_dt timestamp(0),
update_dt timestamp(0)
);
create table YOUR-USERNAME.XXXX_meta(
schema_name varchar(30),
table_name varchar(30),
max_update_dt timestamp(0)
);
insert into YOUR-USERNAME.XXXX_meta( schema_name, table_name, max_update_dt )
values( 'YOUR-USERNAME','XXXX_SOURCE', to_timestamp('1900-01-01','YYYY-MM-DD') );
create table YOUR-USERNAME.XXXX_stg_del(
id integer
);
----------------------------------------------------------------------------
-- Инкрементальная загрузка
-- 1. Очистка стейджинговых таблиц
delete from YOUR-USERNAME.XXXX_stg;
delete from YOUR-USERNAME.XXXX_stg_del;
-- 2. Захват данных из источника (измененных с момента последней загрузки) в стейджинг
insert into YOUR-USERNAME.XXXX_stg( id, val, update_dt )
select id, val, update_dt from YOUR-USERNAME.XXXX_source
where update_dt > ( select max_update_dt from YOUR-USERNAME.XXXX_meta where schema_name='YOUR-USERNAME' and table_name='XXXX_SOURCE' );
-- 3. Захват в стейджинг ключей из источника полным срезом для вычисления удалений.
insert into YOUR-USERNAME.XXXX_stg_del( id )
select id from YOUR-USERNAME.XXXX_source;
-- 4. Загрузка в приемник "вставок" на источнике (формат SCD1).
insert into YOUR-USERNAME.XXXX_target( id, val, create_dt, update_dt )
select
stg.id,
stg.val,
stg.update_dt,
null
from YOUR-USERNAME.XXXX_stg stg
left join YOUR-USERNAME.XXXX_target trg
on stg.id = trg.id
where trg.id is null;
-- 5. Обновление в приемнике "обновлений" на источнике (формат SCD1).
update YOUR-USERNAME.XXXX_target
set
val = tmp.val,
update_dt = tmp.update_dt
from (
select
stg.id,
stg.val,
stg.update_dt,
null
from YOUR-USERNAME.XXXX_stg stg
inner join YOUR-USERNAME.XXXX_target trg on stg.id = trg.id
where stg.val <> trg.val
or ( stg.val is null and trg.val is not null ) or ( stg.val is not null and trg.val is null )
) tmp
where XXXX_target.id = tmp.id;
-- 6. Удаление в приемнике удаленных в источнике записей (формат SCD1).
delete from YOUR-USERNAME.XXXX_target
where id in (
select trg.id
from YOUR-USERNAME.XXXX_target trg
left join YOUR-USERNAME.XXXX_stg_del stg
on stg.id = trg.id
where stg.id is null
);
-- 7. Обновление метаданных.
update YOUR-USERNAME.XXXX_meta
set max_update_dt = coalesce( (select max( update_dt ) from YOUR-USERNAME.XXXX_stg ), ( select max_update_dt from YOUR-USERNAME.XXXX_meta where schema_name='YOUR-USERNAME' and table_name='XXXX_SOURCE' ) )
where schema_name='YOUR-USERNAME' and table_name = 'XXXX_SOURCE';
-- 8. Фиксация транзакции.
commit;
SCD1_incremental_full_script.sql
SCD1_incremental_full_script.sql. На экране
$ wget https://drive.google.com/file/d/13WSK0C1Z36hhsopebgEv2bGLAoxEsLUp/view?usp=drive_web&authuser=0
$ unzip data.zip
Task:
Разработка Хранилище данных. Создадите таблицы в базе
Decision:
$ vim main.ddl
$ cat main.ddl
#!/usr/bin/python3
----------------------------------
create table YOUR-USERNAME.XXXX_stg_transactions(
trans_id varchar(15),
trans_date timestamp(0),
--amt decimal(10,2),
amt numeric(10,2),
card_num varchar(20),
oper_type varchar(10),
oper_result varchar(10),
terminal varchar(10),
create_dt timestamp(0),
update_dt timestamp(0));
create table YOUR-USERNAME.XXXX_dwh_fact_transactions(
trans_id varchar(15),
trans_date timestamp(0),
--amt numeric(10,2),
amt decimal(10,2),
card_num varchar(20),
oper_type varchar(10),
oper_result varchar(10),
terminal varchar(10),
create_dt timestamp(0),
update_dt timestamp(0));
----------------------------------
create table YOUR-USERNAME.XXXX_stg_terminals(
terminal_id varchar(10),
terminal_type varchar(10),
terminal_city varchar(30),
terminal_address varchar(70),
create_dt timestamp(0),
update_dt timestamp(0));
create table YOUR-USERNAME.XXXX_dwh_dim_terminals(
terminal_id varchar(10),
terminal_type varchar(10),
terminal_city varchar(30),
terminal_address varchar(70),
create_dt timestamp(0),
update_dt timestamp(0),
effective_from timestamp(0),
effective_to timestamp(0),
deleted_flg char(1));
create table YOUR-USERNAME.XXXX_stg_del_terminals(
terminal_id varchar(10));
----------------------------------
create table YOUR-USERNAME.XXXX_stg_blacklist(
passport_num varchar(15),
--entry_dt date
entry_dt timestamp(0),
create_dt timestamp(0),
update_dt timestamp(0));
create table YOUR-USERNAME.XXXX_dwh_fact_passport_blacklist(
passport_num varchar(30),
--entry_dt date
--date timestamp(0)
entry_dt timestamp(0),
create_dt timestamp(0),
update_dt timestamp(0));
----------------------------------
create table YOUR-USERNAME.XXXX_stg_cards(
card_num varchar(20),
account_num varchar(20),
create_dt timestamp(0),
update_dt timestamp(0));
create table YOUR-USERNAME.XXXX_dwh_dim_cards(
card_num varchar(20),
account_num varchar(20),
--create_dt date,
--update_dt date
create_dt timestamp(0),
update_dt timestamp(0),
effective_from timestamp(0),
effective_to timestamp(0),
deleted_flg char(1));
create table YOUR-USERNAME.XXXX_stg_del_cards(
card_num varchar(20));
----------------------------------
create table YOUR-USERNAME.XXXX_stg_accounts(
account_num varchar(20),
--valid_to date,
valid_to timestamp(0),
client varchar(10),
create_dt timestamp(0),
update_dt timestamp(0));
create table YOUR-USERNAME.XXXX_dwh_dim_accounts(
account_num varchar(20),
--valid_to date,
valid_to timestamp(0),
client varchar(10),
--create_dt date,
--update_dt date
create_dt timestamp(0),
update_dt timestamp(0),
effective_from timestamp(0),
effective_to timestamp(0),
deleted_flg char(1));
create table YOUR-USERNAME.XXXX_stg_del_accounts(
account_num varchar(20));
----------------------------------
create table YOUR-USERNAME.XXXX_stg_clients(
client_id varchar(10),
last_name varchar(20),
first_name varchar(20),
patronymic varchar(20),
--date_of_birth date,
date_of_birth timestamp(0),
passport_num varchar(15),
--passport_valid_to date,
passport_valid_to timestamp(0),
phone varchar(16),
create_dt timestamp(0),
update_dt timestamp(0));
create table YOUR-USERNAME.XXXX_dwh_dim_clients(
client_id varchar(10),
last_name varchar(20),
first_name varchar(20),
patronymic varchar(20),
--date_of_birth date,
date_of_birth timestamp(0),
passport_num varchar(15),
--passport_valid_to date,
passport_valid_to timestamp(0),
phone varchar(16),
--create_dt date,
--update_dt date
create_dt timestamp(0),
update_dt timestamp(0),
effective_from timestamp(0),
effective_to timestamp(0),
deleted_flg char(1));
create table YOUR-USERNAME.XXXX_stg_del_clients(
client_id varchar(10));
----------------------------------
create table YOUR-USERNAME.XXXX_rep_fraud(
event_dt timestamp(0),
passport varchar(20),
fio varchar(50),
phone varchar(16),
event_type varchar(120),
--report_dt date
report_dt timestamp(0));
----------------------------------
create table YOUR-USERNAME.XXXX_meta(
schema_name varchar(30),
table_name varchar(30),
max_update_dt timestamp(0));
----------------------------------
Task:
Разработка Хранилище данных. Алгоритм для файла main.py: Подключитесь к двум базам, Очистите весь стейджинг. Загрузите файлы transactions_01032021.txt, terminals_01032021.xlsx, passport_blacklist_01032021.xlsx в стейджинг. Загрузите таблицы clients, accounts, cards в стейджинг. Используйте следующий подход: Выполните запрос к базе 1, Сохраните полученный результат в DataFrame, Загрузите DataFrame в базу 2. Загрузите данные из стейджинга в целевую таблицу xxxx_dwh_dim_terminals, xxxx_dwh_dim_cards, xxxx_dwh_dim_accounts, xxxx_dwh_dim_clients. Загрузите данные из стейджинга в целевую таблицу xxxx_dwh_fact_passport_blacklist, xxxx_dwh_fact_transactions.. Фактовые таблицы данные перекладываются «простым инсертом», то есть необходимо выполнить один INSERT INTO ... SELECT .... Напишите скрипт, соединяющий нужные таблицы для поиска операций, совершенных при недействующем договоре. Отладьте ваш скрипт для одной даты PgAdmin, он должен выдавать результат. Результат выполнения скрипта загружайте в таблицу xxxx_rep_fraud. Незабывайте сформировать поле report_dt. Зафиксируйте изменения. Отключитесь от баз. Переименуйте обработанные файлы и перенесите их в другой каталог. отладить его работоспособность навсех трех днях загрузки.
Decision:
$ vim main.py
$ cat main.py
#!/usr/bin/python3
import pandas as pd
import psycopg2
import os
###################Подключение к базам
#conn_YOUR-DB1 = psycopg2.connect(database = "YOUR-DB",host = "YOUR-IP",user = "YOUR-USERNAME",password = "YOUR-PASSWORD",port = "5432")
conn_YOUR-DB1 = psycopg2.connect(database = "YOUR-DB1",host = "YOUR-HOST",user = "YOUR-USERNAME1",password = "YOUR-PASSWORD1",port = "5432")
conn_YOUR-DB2 = psycopg2.connect(database = "bank",host = "YOUR-HOST",user = "YOUR-USERNAME2",password = "YOUR-PASSWORD2",port = "5432")
conn_YOUR-DB1.autocommit = False
conn_YOUR-DB2.autocommit = False
cursor_YOUR-DB1 = conn_YOUR-DB1.cursor()
cursor_YOUR-DB2 = conn_YOUR-DB2.cursor()
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM YOUR-USERNAME1.XXXX_stg_transactions;""")
###################Загрузка данных в стейджинг
#df = pd.read_csv(f'/home/YOUR-USERNAME1/XXXX/project/data/transactions_01032021.txt', sep=';', decimal=',', header=0, index_col=None)
dirpath = "/home/YOUR-USERNAME1/XXXX/project"
project_files = os.listdir(dirpath)
for transactions in project_files:
if transactions.endswith('.txt'):
df = pd.read_csv(transactions, sep=";")
datenow_w_txt = transactions.rsplit('_')[1]
datenow_str = datenow_w_txt.split('.')[0]
df['amount'] = df['amount'].map(lambda z: z.strip().replace(',', '.')).astype('float')
df['create_dt'] = datenow_str
df['update_dt'] = datenow_str
###################
cursor_YOUR-DB1.executemany("""
INSERT INTO YOUR-USERNAME1.XXXX_stg_transactions(trans_id, trans_date, amt, card_num, oper_type, oper_result, terminal , create_dt, update_dt)
VALUES(%s, %s , %s , %s , %s , %s , %s, to_timestamp(%s,'DDMMYYYY'), to_timestamp(%s,'DDMMYYYY'))
""", df.values.tolist())
###################Загрузка данных в целевые таблицы фактов
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_dwh_fact_transactions (trans_id,trans_date,card_num,oper_type,amt,oper_result,terminal,create_dt,update_dt)
SELECT stg.trans_id,stg.trans_date,stg.card_num,stg.oper_type,stg.amt,stg.oper_result,stg.terminal,stg.create_dt,stg.update_dt
FROM YOUR-USERNAME1.XXXX_stg_transactions as stg;
""")
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM YOUR-USERNAME1.XXXX_stg_terminals;""")
###################Загрузка данных в стейджинг
#df = pd.read_excel(f'/home/YOUR-USERNAME1/XXXX/project/data/terminals_01032021.xlsx', sheet_name='terminals', header=0, index_col=None)
for terminals in project_files:
if terminals.startswith('terminals'):
df = pd.read_excel(terminals, sheet_name='terminals', header=0, index_col=None )
datenow_w_txt = terminals.rsplit('_')[1]
datenow_str = datenow_w_txt.split('.')[0]
df['create_dt'] = datenow_str
df['update_dt'] = datenow_str
#df.insert(4, 'update_dt','2021-03-01')
###################
cursor_YOUR-DB1.executemany("""
INSERT INTO YOUR-USERNAME1.XXXX_stg_terminals(terminal_id,terminal_type,terminal_city,terminal_address,create_dt,update_dt)
VALUES(%s,%s,%s,%s,to_timestamp(%s,'DDMMYYYY'),to_timestamp(%s,'DDMMYYYY'));
""", df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_dwh_dim_terminals (terminal_id,terminal_type,terminal_city,terminal_address,create_dt,update_dt)
SELECT stg.terminal_id, stg.terminal_type, stg.terminal_city, stg.terminal_address, stg.update_dt,to_timestamp('9999-12-31', 'YYYY-MM-DD')
from YOUR-USERNAME1.XXXX_stg_terminals as stg
left join YOUR-USERNAME1.XXXX_dwh_dim_terminals as trg
on stg.terminal_id = trg.terminal_id
where trg.terminal_id is null;
""")
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM YOUR-USERNAME1.XXXX_stg_blacklist;""")
###################Загрузка данных в стейджинг
#df = pd.read_excel(f'/home/YOUR-USERNAME1/XXXX/project/data/passport_blacklist_01032021.xlsx', sheet_name='blacklist', header=0, index_col=None)
for blacklist in project_files:
if blacklist.startswith('passport_blacklist'):
df = pd.read_excel(blacklist, sheet_name='blacklist', header=0, index_col=None )
datenow_w_ext = blacklist.rsplit('blacklist_')[1]
datenow_str = datenow_w_ext.split('.')[0]
df['create_dt'] = datenow_str
df['update_dt'] = datenow_str
###################
cursor_YOUR-DB1.executemany("""
INSERT INTO YOUR-USERNAME1.XXXX_stg_blacklist(entry_dt,passport_num,create_dt,update_dt)
VALUES(%s, %s,to_timestamp(%s,'DDMMYYYY'),to_timestamp(%s,'DDMMYYYY'));
""", df.values.tolist())
###################Загрузка данных в целевые таблицы фактов
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_dwh_fact_passport_blacklist
SELECT stg.passport_num,stg.entry_dt,stg.create_dt,stg.update_dt
from YOUR-USERNAME1.XXXX_stg_blacklist as stg
left join YOUR-USERNAME1.XXXX_dwh_fact_passport_blacklist as trg
on stg.passport_num = trg.passport_num
where trg.passport_num is null;
""")
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM YOUR-USERNAME1.XXXX_stg_cards;""")
###################Загрузка данных в стейджинг
cursor_YOUR-DB2.execute("""
--SELECT regexp_replace(card_num, '\s+$', '') as card_num,account,create_dt,update_dt
SELECT card_num,account,create_dt,update_dt
FROM info.cards
""")
records = cursor_YOUR-DB2.fetchall()
#for row in records:
# print(row)
names = [ x[0] for x in cursor_YOUR-DB2.description ]
df = pd.DataFrame( records, columns = names )
cursor_YOUR-DB1.executemany("""
INSERT INTO YOUR-USERNAME1.XXXX_stg_cards(card_num,account_num,create_dt,update_dt)
VALUES(%s,%s,%s,%s)
""", df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_dwh_dim_cards (card_num,account_num,create_dt,update_dt)
SELECT stg.card_num, stg.account_num, stg.create_dt, to_timestamp('9999-12-31', 'YYYY-MM-DD')
from YOUR-USERNAME1.XXXX_stg_cards as stg
left join YOUR-USERNAME1.XXXX_dwh_dim_cards as trg
on stg.card_num = trg.card_num
where trg.card_num is null;
""")
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM YOUR-USERNAME1.XXXX_stg_accounts;""")
###################Загрузка данных в стейджинг
cursor_YOUR-DB2.execute("""
SELECT account,valid_to,client,create_dt,update_dt
FROM info.accounts
""")
records = cursor_YOUR-DB2.fetchall()
#for row in records:
# print(row)
names = [ x[0] for x in cursor_YOUR-DB2.description ]
df = pd.DataFrame( records, columns = names )
cursor_YOUR-DB1.executemany("""
INSERT INTO YOUR-USERNAME1.XXXX_stg_accounts(account_num,valid_to,client,create_dt,update_dt)
VALUES( %s, %s, %s, %s, %s)
""", df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_dwh_dim_accounts(account_num,valid_to,client,create_dt,update_dt)
SELECT stg.account_num,stg.valid_to,stg.client,stg.create_dt,to_timestamp('9999-12-31', 'YYYY-MM-DD')
from YOUR-USERNAME1.XXXX_stg_accounts as stg
left join YOUR-USERNAME1.XXXX_dwh_dim_accounts as trg
on stg.account_num = trg.account_num
where trg.account_num is null;
""")
###################
######################################
###################Очистка стейджинговых таблиц
cursor_YOUR-DB1.execute("""DELETE FROM YOUR-USERNAME1.XXXX_stg_clients;""")
###################Загрузка данных в стейджинг
cursor_YOUR-DB2.execute("""
SELECT client_id,last_name,first_name,patronymic,date_of_birth,passport_num,passport_valid_to,phone,create_dt,update_dt
FROM info.clients;
""")
records = cursor_YOUR-DB2.fetchall()
#for row in records:
# print(row)
names = [ x[0] for x in cursor_YOUR-DB2.description ]
df = pd.DataFrame(records, columns = names)
cursor_YOUR-DB1.executemany("""
INSERT INTO YOUR-USERNAME1.XXXX_stg_clients(client_id,last_name,first_name,patronymic,date_of_birth,passport_num,passport_valid_to,phone,create_dt,update_dt)
VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);
""", df.values.tolist())
###################Загрузка данных в целевые таблицы измерений
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_dwh_dim_clients(client_id,last_name,first_name,patronymic,date_of_birth,passport_num,passport_valid_to,phone,create_dt,update_dt)
SELECT stg.client_id,stg.last_name,stg.first_name,stg.patronymic,stg.date_of_birth,stg.passport_num,stg.passport_valid_to,stg.phone,stg.create_dt,now()
from YOUR-USERNAME1.XXXX_stg_clients as stg
left join YOUR-USERNAME1.XXXX_dwh_dim_clients as trg
on stg.client_id = trg.client_id
where trg.client_id is null;
""")
###################
######################################
###################Выявление мошеннических операций и построение отчёта
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_rep_fraud
select
min(t2.trans_date) as trans_date,
tgddcl.passport_num as passport_num,
(tgddcl.last_name || ' ' || tgddcl.first_name || ' ' || tgddcl.patronymic ) as fio,
tgddcl.phone as phone,
'1' as event_type,
now() as report_dt
from (
select *
from (
with current_dt as (
select trans_date
from YOUR-USERNAME1.XXXX_stg_transactions)
select tgdft.*, tgddca.account_num
from YOUR-USERNAME1.XXXX_dwh_fact_transactions as tgdft
left join YOUR-USERNAME1.XXXX_dwh_dim_cards as tgddca
on trim(tgdft.card_num) = trim(tgddca.card_num )
where tgdft.oper_result = 'SUCCESS'
and tgdft.trans_date in (
select trans_date
from current_dt)) as t
left join YOUR-USERNAME1.XXXX_dwh_dim_accounts as gdda
on t.account_num = gdda.account_num ) as t2
left join YOUR-USERNAME1.XXXX_dwh_dim_clients as tgddcl
on t2.client = tgddcl.client_id
where (tgddcl.passport_valid_to < t2.trans_date
or tgddcl.passport_num in (
select passport_num
from YOUR-USERNAME1.XXXX_dwh_fact_passport_blacklist))
group by tgddcl.passport_num, (tgddcl.last_name || ' ' || tgddcl.first_name || ' ' || tgddcl.patronymic ), tgddcl.phone;
""")
###################Совершение операции при недействующем договоре.
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_rep_fraud
select
t2.trans_date as trans_date,
gddcl.passport_num as passport_num,
(gddcl.last_name || ' ' || gddcl.first_name || ' ' || gddcl.patronymic ) as fio,
gddcl.phone as phone,
'2' as event_type,
now() as report_dt
from (
select min(t.trans_date) trans_date, t.account_num, gdda.client
from (
with current_dt as (
select trans_date
from YOUR-USERNAME1.XXXX_stg_transactions)
select gdft.trans_date, gdft.card_num, gddca.account_num
from YOUR-USERNAME1.XXXX_dwh_fact_transactions as gdft
left join YOUR-USERNAME1.XXXX_dwh_dim_cards as gddca
on trim(gdft.card_num) = trim(gddca.card_num )
where gdft.oper_result = 'SUCCESS'
and gdft.trans_date in (
select trans_date
from current_dt)) as t
left join YOUR-USERNAME1.XXXX_dwh_dim_accounts as gdda
on t.account_num = gdda.account_num
where t.trans_date > gdda.valid_to
group by t.account_num, gdda.client ) as t2
left join YOUR-USERNAME1.XXXX_dwh_dim_clients as gddcl
on t2.client = gddcl.client_id;
""")
###################Совершение операций в разных городах в течение одного часа
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_rep_fraud
select
t3.trans_date,
t3.passport_num,
(t3.last_name || ' ' || t3.first_name || ' ' || t3.patronymic ) as fio,
t3.phone ,
'3' as event_type,
now() as report_dt
from (
select trans_date, trg2.*
from (
select * from (
select * from (
with lds as (
select
trans_date,
terminal_city,
card_num,
lag(terminal_city) over (
partition by gdftr.card_num
order by gdftr.trans_date) as lag_c,
lag(trans_date) over (
partition by card_num
order by gdftr.trans_date) as lag_t,
lead(terminal_city) over (
partition by gdftr.card_num
order by gdftr.trans_date) as lead_c ,
lead(trans_date) over (
partition by card_num
order by gdftr.trans_date) as lead_t
from YOUR-USERNAME1.XXXX_dwh_fact_transactions as gdftr
left join YOUR-USERNAME1.XXXX_dwh_dim_terminals as gddt
on gdftr.terminal = gddt.terminal_id
where gdftr.oper_result = 'SUCCESS'),
current_dt as (
select trans_date
from YOUR-USERNAME1.XXXX_stg_transactions )
select
min(trans_date) as trans_date,
card_num
from lds
where lag_c <> terminal_city
and (trans_date-lag_t) < '01:00:00'
and trans_date in (
select trans_date
from current_dt)
group by card_num) as trg
left join YOUR-USERNAME1.XXXX_dwh_dim_cards as trg3
on trim(trg.card_num) = trim(trg3.card_num )) as t
left join YOUR-USERNAME1.XXXX_dwh_dim_accounts as trg4
on t.account_num = trg4.account_num ) as t2
left join YOUR-USERNAME1.XXXX_dwh_dim_clients as trg2
on t2.client = trg2.client_id ) as t3;
""")
###################Попытка подбора суммы
cursor_YOUR-DB1.execute("""
INSERT INTO YOUR-USERNAME1.XXXX_rep_fraud
select
t3.trans_date,
t3.passport_num ,
(t3.last_name || ' ' || t3.first_name || ' ' || t3.patronymic ) as fio,
t3.phone ,
'4' as event_type,
now() as report_dt
from (
select
trans_date,
gddcl.*
from (
select * from (
select * from(
with rj as (
select
*,
lag(amt) over (
partition by gdft.card_num
order by gdft.trans_date) as lag_a,
lag(amt,2) over (
partition by gdft.card_num
order by gdft.trans_date) as lag_a2,
lag(amt,3) over (
partition by gdft.card_num
order by gdft.trans_date) as lag_a3,
lag(oper_result) over (
partition by gdft.card_num
order by gdft.trans_date) as lag_r,
lag(oper_result,2) over (
partition by gdft.card_num
order by gdft.trans_date) as lag_r2,
lag(oper_result,3) over (
partition by gdft.card_num
order by gdft.trans_date) as lag_r3,
lag(trans_date,3) over (
partition by gdft.card_num
order by gdft.trans_date) as min_t
from YOUR-USERNAME1.XXXX_dwh_fact_transactions gdft),
current_dt as (
select trans_date
from YOUR-USERNAME1.XXXX_stg_transactions )
select *
from rj
where oper_result = 'SUCCESS'
and lag_r = 'REJECT'
and lag_a > amt
and lag_r2 = 'REJECT'
and lag_a2 > lag_a
and lag_r3 = 'REJECT'
and lag_a3 > lag_a2
and (trans_date - min_t) <= '00:20:00'
and trans_date in (
select trans_date
from current_dt)) as trg
left join YOUR-USERNAME1.XXXX_dwh_dim_cards as gddca
on trim(trg.card_num) = trim(gddca.card_num )) as t
left join YOUR-USERNAME1.XXXX_dwh_dim_accounts as gdda
on t.account_num = gdda.account_num ) as t2
left join YOUR-USERNAME1.XXXX_dwh_dim_clients as gddcl
on t2.client = gddcl.client_id ) as t3;
""")
######################################
conn_YOUR-DB2.commit()
conn_YOUR-DB1.commit()
######################################Запись файлов в архив
os.rename('/home/YOUR-USERNAME1/XXXX/project/terminals_01032021.xlsx', '/home/YOUR-USERNAME1/XXXX/project/archive/terminals_01032021.xlsx.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/transactions_01032021.txt', '/home/YOUR-USERNAME1/XXXX/project/archive/transactions_01032021.txt.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/passport_blacklist_01032021.xlsx', '/home/YOUR-USERNAME1/XXXX/project/archive/passport_blacklist_01032021.xlsx.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/terminals_02032021.xlsx', '/home/YOUR-USERNAME1/XXXX/project/archive/terminals_02032021.xlsx.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/transactions_02032021.txt', '/home/YOUR-USERNAME1/XXXX/project/archive/transactions_02032021.txt.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/passport_blacklist_02032021.xlsx', '/home/YOUR-USERNAME1/XXXX/project/archive/passport_blacklist_02032021.xlsx.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/terminals_03032021.xlsx', '/home/YOUR-USERNAME1/XXXX/project/archive/terminals_03032021.xlsx.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/transactions_03032021.txt', '/home/YOUR-USERNAME1/XXXX/project/archive/transactions_03032021.txt.backup')
os.rename('/home/YOUR-USERNAME1/XXXX/project/passport_blacklist_03032021.xlsx', '/home/YOUR-USERNAME1/XXXX/project/archive/passport_blacklist_03032021.xlsx.backup')
######################################
cursor_YOUR-DB2.close();
cursor_YOUR-DB1.close();
$ sudo apt install python3.10-venv
$ python3.10 -m venv venv
$ source venv/bin/activate
$ pip install pandas
$ python3 main.py
$ ls archive/
passport_blacklist_01032021.xlsx.backup terminals_01032021.xlsx.backup transactions_01032021.txt.backup
$ sudo psql -U YOUR-USERNAME
YOUR-USERNAME=# select * from YOUR-USERNAME.XXXX_rep_fraud;
Task:
Администрирование локальных, виртуальных и облачных серверов. Заполните файл main.cron расписанием и командой исполнения вашего скрипта. Расписание установите так, как считаете нужным чтобы ваши данные заполнились корректно.
Decision:
$ vim main.cron
$ cat main.cron
0 0 * * * /home/david/project/main.py
Decision:
По следующим признакам получилось выявить мошеннические транзакции: выполнение операции с просроченным или заблокированным паспортом, выполнение операции с недействительным контрактом, выполнение операций в разных городах в течение одного часа
Decision:
Защитил диплом о профессиональной переподготовке:
Source:
# https://dzen.ru/a/Ypr65Wh4jmLimA3o
# https://www.youtube.com/watch?v=Je3Y8up0Qbs&list=LL&index=5&t=98s
# https://losst.pro/kak-posmotret-otkrytye-porty-v-linux?ysclid=lpe5qjsv9o445640330
# https://www.pgadmin.org/download/pgadmin-4-apt/
# https://losst.pro/spisok-kontejnerov-docker
2019-11-01 - 2021-05-30: Easy School, Иркутск. Должность: English Level Elementary A - Дополнительное образование. Дополнительная информация: Навыки - English, Python, Clouds. Достижения: Разработал программу телеграмм бот Переводчик.
Show
Цель:
# Написать функцию Меню без бота.
# Раработка программы Переводчик без бота.
# Использовать программу Переводчик с готовой функцией Меню.
# Разработать базу данных с таблицами Словарь.
# Разработать Телеграм Бот с программой Переводчик, используя Aiogram.
# Добавить в Телеграм Бот Psycopg2, для написания Sql запросов.
# Разработать Телеграм Бот с программой Переводчик, используя Telebot вместо Aiogram.
# Добавить информацию в базу данных в телеграм бот.
# Перенести все логины и пароли в отдельный файл.
# Применить в Телеграм Бот программ Переводчик.
# Настроить службу для ручного запуска Бота.
Skills:
# Разработка Телеграм ботов.
# Написание Sql запросов.
# Администрирование локальных, виртуальных и облачных серверов.
Task:
Разработка функции меню.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyMenu.py
root@kvmubuntu:~# cat pyMenu.py
def fun(t1, b, t2, l, t3):
while True:
cmd1 = input("Привет!\nЯ тестовый бот.\nВыбери программу, которую ты хочешь выполнить\n(0, translator, dictionary): ")
if cmd1 == "0":
break
elif cmd1 == t1:
while True:
cmd2 = input("Что именно нужно сделать\n(translate, back): ")
if cmd2 == b:
print('Вы вернулись в главное меню')
break
elif cmd2 == t2:
print("Здесь программа переведет вам текст")
else:
print("Я такую команду не знаю")
elif cmd1 == "dictionary":
while True:
cmd2 = input("Что именно нужно сделать\n(list,term,back): ")
if cmd2 == b:
print('Вы вернулись в главное меню')
break
elif cmd2 == l:
print("Вывести список слов")
elif cmd2 == t3:
print("Выбрать слово их списка")
else:
print("Я такую команду не знаю")
else:
print("Я такую команду не знаю")
return t1
print(fun("translator","back","translate","list","term"))
root@kvmubuntu:~# python3 pyMenu.py
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): translator
Что именно нужно сделать
(translate, back): translate
Здесь программа переведет вам текст
Что именно нужно сделать
(translate, back): back
Вы вернулись в главное меню
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): dictionary
Что именно нужно сделать
(list,term,back): list
Вывести список слов
Что именно нужно сделать
(list,term,back): term
Выбрать слово их списка
Что именно нужно сделать
(list,term,back): back
Вы вернулись в главное меню
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): 0
translator
Task:
Подготовка библиотек.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# python3 -m venv tenv
root@kvmubuntu:~# source tenv/bin/activate
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
translate==3.6.1
pyTelegramBotAPI==4.21.0
googletrans==3.1.0a0
langdetect==1.0.9
aiogram==2.23.1
psycopg2-binary==2.9.9
root@kvmubuntu:~# pip install -r requirements.txt
Task:
Раработка программы переводчик.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslator.py
root@kvmubuntu:~# cat pyTranslator.py
from translate import Translator
translator=Translator(from_lang="russian", to_lang="english")
translation=translator.translate("Вчера я забронировал у вас номер в отеле.")
print(translation)
root@kvmubuntu:~# python3 translator.py
Hi, how are you?
root@kvmubuntu:~# vim pyTranslator1.py
root@kvmubuntu:~# cat pyTranslator1.py
from translate import Translator
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
cmd3 = input("Введите текст, который вы хотите перевести: ")
if cmd3[0].lower() in ru_letters:
translator = Translator(from_lang="russian", to_lang="english")
elif cmd3[0].lower() in en_letters:
translator = Translator(from_lang="english", to_lang="russian")
else:
print('Я тебя не понимаю')
translation = translator.translate(cmd3)
print(translation)
root@kvmubuntu:~# python3 pyTranslator1.py
Введите текст, который вы хотите перевести: Вчера я забронировал у вас номер в отеле.
Tom booked a room at the hotel
root@kvmubuntu:~# vim pyTranslator2.py
root@kvmubuntu:~# cat pyTranslator2.py
from googletrans import Translator
from langdetect import detect
translator = Translator()
cmd3 = input("Введите текст, который вы хотите перевести: ")
translation = translator.translate(cmd3, src=detect(cmd3), dest='en').text
print(translation.encode('utf-8', 'replace').decode())
root@kvmubuntu:~# python3 pyTranslator2.py
Введите текст, который вы хотите перевести: Вчера я забронировал у вас номер в отеле.
Yesterday I booked a hotel room with you.
root@kvmubuntu:~# vim pyTranslator3.py
root@kvmubuntu:~# cat pyTranslator3.py
from googletrans import Translator
from langdetect import detect
translator = Translator()
cmd3 = input("Введите текст, который вы хотите перевести: ")
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
if cmd3[0].lower() in ru_letters:
translation = translator.translate(cmd3, src=detect(cmd3), dest='en').text
print(translation.encode('utf-8', 'replace').decode())
elif cmd3[0].lower() in en_letters:
translation = translator.translate(cmd3, src=detect(cmd3), dest='ru').text
print(translation.encode('utf-8', 'replace').decode())
else:
print('Я тебя не понимаю')
root@kvmubuntu:~# python3 pyTranslator3.py
Введите текст, который вы хотите перевести: Для тестирования работоспособности телеграм бота переводчик, написанном на Python мы использовали готовый преднастроенный телеграм бот сервер.
To test the functionality of the telegram bot translator, written in Python, we used a ready-made pre-configured telegram bot server.
root@kvmubuntu:~# python3 pyTranslator3.py
Введите текст, который вы хотите перевести: To test the functionality of the telegram bot translator, written in Python, we used a ready-made pre-configured telegram bot server.
Для проверки работоспособности переводчика телеграмм-бота, написанного на Python, мы использовали готовый, предварительно настроенный сервер телеграм-бота.
root@kvmubuntu:~# python3 pyTranslator3.py
Введите текст, который вы хотите перевести: ;
Я тебя не понимаю
Task:
Применение библиотеки Translator и готовой функции меню.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyMenuTranslator.py
root@kvmubuntu:~# cat pyMenuTranslator.py
from translate import Translator
def fun(t1, b, t2, l, t3):
while True:
cmd1 = input("Привет!\nЯ тестовый бот.\nВыбери программу, которую ты хочешь выполнить\n(0, translator, dictionary): ")
if cmd1 == "0":
break
elif cmd1 == t1:
while True:
cmd2 = input("Что именно нужно сделать\n(translate, back): ")
if cmd2 == b:
print('Вы вернулись в главное меню')
break
elif cmd2 == t2:
cmd3 = input("Введите текст, который вы хотите перевести: ")
t4=Translator(from_lang="russian", to_lang="english")
translation=t4.translate(cmd3)
print(translation)
else:
print("Я такую команду не знаю")
elif cmd1 == "dictionary":
while True:
cmd2 = input("Что именно нужно сделать\n(list,term,back): ")
if cmd2 == b:
print('Вы вернулись в главное меню')
break
elif cmd2 == l:
print("Вывести список слов")
elif cmd2 == t3:
print("Выбрать слово их списка")
else:
print("Я такую команду не знаю")
else:
print("Я такую команду не знаю")
return t1
print(fun("translator","back","translate","list","term"))
root@kvmubuntu:~# python3 pyMenuTranslator.py
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): translator
Что именно нужно сделать
(translate, back): translate
Введите текст, который вы хотите перевести: Привет. Как дела?
Hi, how are you?
Что именно нужно сделать
(translate, back): back
Вы вернулись в главное меню
Привет!
Я тестовый бот.
Выбери программу, которую ты хочешь выполнить
(0, translator, dictionary): 0
translator
Task:
Разработать схему БД Словарь.
# Написание Sql запросов.
Decision:
Task:
Разработка базы данных.
Словарь
id | words | translate
1 | Текст1 | Text1
2 | Текст2 | Text2
3 | Текст3 | Text3
Термины
id | words_id | description | translate
1 | 1 | Текст4 | Text4
2 | 3 | Текст5 | Text5
# Написание Sql запросов.
Decision:
root@kvmubuntu:~# psql -U tuser -d tbase2 -h tipubuntu
tbase2=# CREATE TABLE tbase2 (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
words VARCHAR(1000),
translate VARCHAR(1000)
);
tbase2=# CREATE TABLE Terms (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
words_id INT,
description VARCHAR(1000),
translate VARCHAR(1000),
CONSTRAINT fk_tbase2
FOREIGN KEY(words_id)
REFERENCES tbase2(id)
);
tbase2=# INSERT INTO tbase2 (words, translate)
VALUES ('Текст1', 'Text1'), ('Текст2', 'Text2'), ('Текст3', 'Text3');
tbase2=# INSERT INTO Terms (words_id, description, translate)
VALUES (1, 'Текст4', 'Text4'), (3, 'Текст5','Text5');
Task:
Вывести таблицу такой формы:
words | translate | description | translate
Текст1 | Text1 | Текст4 | Text4
Текст3 | Text3 | Текст5 | Text5
# Написание Sql запросов.
Decision:
tbase2=# select words, dictionary.translate, description, Terms.translate
from dictionary
inner join Terms
on dictionary.id = Terms.words_id;
words | translate | description | translate
--------+-----------+-------------+-----------
Текст1 | Text1 | Текст4 | Text4
Текст3 | Text3 | Текст5 | Text5
(2 rows)
Source:
# https://proglib.io/p/rukovodstvo-po-sql-dlya-nachinayushchih-chast-1-sozdanie-bazy-dannyh-tablic-i-ustanovka-svyazey-mezhdu-tablicami-2022-02-07 - Руководство по SQL для начинающих. Часть 1: создание базы данных, таблиц и установка связей между таблицами.
# https://sql-academy.org/ru/guide/inner-join - Внутреннее соединение INNER JOIN.
Task:
Применение библиотеки Aiogram.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslatorAiogram.py
root@kvmubuntu:~# cat pyTranslatorAiogram.py
import logging
from translate import Translator
from aiogram import Bot, Dispatcher, executor, types
API_TOKEN = 'ttoken'
# Configure logging
logging.basicConfig(level=logging.INFO)
# Initialize bot and dispatcher
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
@dp.message_handler(commands=['start', 'help'])
async def send_welcome(message: types.Message):
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
@dp.message_handler()
async def echo(message: types.Message):
text = message.text
if text[0].lower() in ru_letters:
translator = Translator(from_lang="russian", to_lang="english")
elif text[0].lower() in en_letters:
translator = Translator(from_lang="english", to_lang="russian")
else:
await message.answer('Я тебя не понимаю')
return
translation = translator.translate(text)
await message.answer(translation)
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
root@kvmubuntu:~# python3 pyTranslatorAiogram.py
Source:
# https://www.youtube.com/@shcoder001 - Переводчик бот в telegram на python за 5 минут aiogram.
Task:
Применение библиотеки psycopg2.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyPsql.py
root@kvmubuntu:~# cat pyPsql.py
import psycopg2
try:
# пытаемся подключиться к базе данных
conn = psycopg2.connect(dbname='tbase2', user='tuser', password='tpassword', host='tipubuntu')
except:
# в случае сбоя подключения будет выведено сообщение в STDOUT
print('Can`t establish connection to database')
root@kvmubuntu:~# python3 pyPsql.py
root@kvmubuntu:~# vim pyPsql.py
root@kvmubuntu:~# cat pyPsql.py
import psycopg2
try:
# пытаемся подключиться к базе данных
conn = psycopg2.connect(dbname='tbase2', user='tuser', password='tpassword', host='tipubuntu')
cursor = conn.cursor()
cursor.execute('select words, dictionary.translate, description, Terms.translate from dictionary inner join Terms on dictionary.id = Terms.words_id')
#records = cursor.fetchall()
#print(records)
for row in cursor:
print(row)
cursor.close()
conn.close()
except:
# в случае сбоя подключения будет выведено сообщение в STDOUT
print('Can`t establish connection to database')
root@kvmubuntu:~# python3 pyPsql.py
('Внутреннее соединение', 'Inner join', 'Возвращаются только те строки, где ключевые значения совпадают в обеих таблицах', 'Only those rows are returned where the key values match in both tables')
('Полное соединение', 'Cross Join', 'Позволяет получить декартово произведение нескольких таблиц. особенно полезен, когда между таблицами нет определенной связи, и вам нужно создать полную комбинацию записей из каждой таблицы', '-')
('Декартово произведение', 'Cartesian product', 'Результат соединения строки из первой таблицы с каждой строкой из второй таблицы', '-')
root@kvmubuntu:~# vim pyPsql.py
root@kvmubuntu:~# cat pyPsql.py
import psycopg2
from contextlib import closing
with closing(psycopg2.connect(dbname='dictionary', user='tuser', password='tpassword', host='tipubuntu')) as conn:
with conn.cursor() as cursor:
cursor.execute('select words, dictionary.translate, description, Terms.translate from dictionary inner join Terms on dictionary.id = Terms.words_id')
for row in cursor:
print(row)
root@kvmubuntu:~# python3 pyPsql.py
('Внутреннее соединение', 'Inner join', 'Возвращаются только те строки, где ключевые значения совпадают в обеих таблицах', 'Only those rows are returned where the key values match in both tables')
('Полное соединение', 'Cross Join', 'Позволяет получить декартово произведение нескольких таблиц. особенно полезен, когда между таблицами нет определенной связи, и вам нужно создать полную комбинацию записей из каждой таблицы', '-')
('Декартово произведение', 'Cartesian product', 'Результат соединения строки из первой таблицы с каждой строкой из второй таблицы', '-')
Source:
# https://ru.hexlet.io/blog/posts/python-postgresql - Использование Psycopg2.
# https://khashtamov.com/ru/postgresql-python-psycopg2/ - Начало работы.
Task:
Применение библиотеки Telebot.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslatorMenuTelebot.py
root@kvmubuntu:~# cat pyTranslatorMenuTelebot.py
import telebot
from telebot import types
from googletrans import Translator
from langdetect import detect
TOKEN='tkey'
bot = telebot.TeleBot(TOKEN)
translator = Translator()
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
@bot.message_handler(commands=["start"])
def start(message):
message_user = f"Привет, <b>{message.from_user.first_name.title()}</b>! Я тестовый бот.\n" \
f"<b>Выбери программу, которую ты хочешь выполнить:</b>\n" \
f"1. Чем полезен данный бот\n" \
f"2. Функции бота (что может данный бот)\n" \
f"3. Для тех кто хочет поддержать нас и наш проект"
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
item1 = types.KeyboardButton(text="Чем полезен бот ?")
item2 = types.KeyboardButton(text="Функции бота")
item3 = types.KeyboardButton(text="Поддержать проект")
markup.add(item1, item2, item3)
bot.send_message(message.from_user.id, message_user, reply_markup=markup, parse_mode='html')
bot.register_next_step_handler(message, impact_KEYBORD_bot)
bot.register_next_step_handler(message, fuctional_KEYBORD_bot)
bot.register_next_step_handler(message, donat_user_bot)
def impact_KEYBORD_bot(message):
if message.text == "Чем полезен бот ?":
message_user = "Этот бот много чем будет полезен для вас. Ознакомьтесь с функционалом бота чтобы понять это." \
" В этого бота со временем мы будем внедрять новые фичи и полезные функции. Чтобы узнавать о новых фишках бота, слидите за нашим Telegramm каналом"
key = types.InlineKeyboardMarkup()
button = types.InlineKeyboardButton(text='Мое портфолио', url="http://dato138it.ru")
key.add(button)
bot.send_message(message.from_user.id, message_user, reply_markup=key, parse_mode='html')
bot.register_next_step_handler(message, fuctional_KEYBORD_bot)
bot.register_next_step_handler(message, donat_user_bot)
def fuctional_KEYBORD_bot(message):
if message.text == 'Функции бота':
message_user = "<b>Добро пожаловать главное меню бота</b>\n\n" \
"В скором будущем мы будем добавлять сюда новые функции!"
key = types.ReplyKeyboardMarkup(resize_keyboard=True)
button0 = types.KeyboardButton("Переводчик")
button1 = types.KeyboardButton("Словарь")
key.add(button0, button1)
bot.send_message(message.from_user.id, message_user, reply_markup=key, parse_mode='html')
bot.register_next_step_handler(message, impact_KEYBORD_bot)
bot.register_next_step_handler(message, donat_user_bot)
bot.register_next_step_handler(message, translate_message)
bot.register_next_step_handler(message, dictionary_message)
def donat_user_bot(message):
if message.text == "Поддержать проект":
message_users = f"<b>Приветствую уважаемый {message.from_user.first_name.title()}</b>, вы перешли в отдел поддержки нашего проекта \n\n" \
f"Мы будем благодарны любой поддержки от вас. И также благодарим, что вы пользуетесь нашим ботом - это главная ваша поддержка для нас!\n\n" \
f"Мы принимаем материальную поддержку на:\n" \
f"<b>1. Donationalerts</b>\n" \
f"<b>2. PAYEER</b>\nномер счёта для пополнения: P1091200672\n" \
f"<b>3. QIWI</b>\n" \
f"<b>4. Тинькофф банк</b>"
key = types.InlineKeyboardMarkup()
button0 = types.InlineKeyboardButton(text="Donationalerts", url="https://www.donationalerts.com/r/tgbot_v")
button1 = types.InlineKeyboardButton(text="PAYEER", url="https://payeer.com/ru/account/send/")
button2 = types.InlineKeyboardButton(text="QIWI", url="https://my.qiwi.com/VLADYSLAV-DTJ4Y_MwOA")
button3 = types.InlineKeyboardButton(text="Тинькофф банк", url="https://www.tinkoff.ru/cf/35TWsWpG8Fe")
key.add(button0, button1, button2, button3)
bot.send_message(message.from_user.id, message_users, reply_markup=key, parse_mode='html')
bot.register_next_step_handler(message, impact_KEYBORD_bot)
bot.register_next_step_handler(message, fuctional_KEYBORD_bot)
@bot.message_handler(content_types=['text'])
def translate_message(message):
if message.text == 'Переводчик':
bot.send_message(message.chat.id, 'Напишите сообщения а я переведу его')
bot.register_next_step_handler(message, translate_message_step_2)
def translate_message_step_2(message):
cmd3 = message.text
if cmd3[0].lower() in ru_letters:
translation = translator.translate(cmd3, src=detect(cmd3), dest='en').text
bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
elif cmd3[0].lower() in en_letters:
translation = translator.translate(cmd3, src=detect(cmd3), dest='ru').text
bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
else:
bot.send_message(message.chat.id, 'Я тебя не понимаю')
def dictionary_message(message):
if message.text == 'Словарь':
bot.send_message(message.chat.id, 'list | term')
#text = message.text
#bot.send_message("TEST")
if __name__ == '__main__':
bot.polling(none_stop=True, interval=0)
root@kvmubuntu:~# python3 pyTranslatorMenuTelebot.py
- /Start
- Привет, David! Я тестовый бот.
Выбери программу, которую ты хочешь выполнить:
1. Чем полезен данный бот
2. Функции бота (что может данный бот)
3. Для тех кто хочет поддержать нас и наш проект
- Функции бота
- Добро пожаловать главное меню бота
В скором будущем мы будем добавлять сюда новые функции!
1. Переводчик
2. Словарь
- Переводчик
- Напишите сообщения, а я переведу его
- Привет, Катя!
- Hello, Katya!
- Переводчик
- Напишите сообщения, а я переведу его
- Dear Kate!
- Дорогая Кейт!
Source:
# https://www.w3schools.com/python/ref_func_input.asp - Python input() Function.
# https://proglib.io/p/samouchitel-po-python-dlya-nachinayushchih-chast-10-uslovnyy-cikl-while-2022-12-22 - Управление бесконечным циклом while в Питоне.
# https://letpy.com/python-guide/functions/ - Функции в Python для начинающих.
# https://ru.stackoverflow.com/questions/1211592/%D0%9A%D0%B0%D0%BA-%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D1%82%D1%8C-%D0%BC%D0%B5%D0%BD%D1%8E-%D1%81-%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BE%D0%BC-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9-%D0%BD%D0%B0-python - Как создать меню с выбором функций на Python?
# https://ru.stackoverflow.com/questions/1341916/%D0%9A%D0%B0%D0%BA-%D0%B7%D0%B0%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%82%D1%8C-%D0%BC%D0%B5%D0%BD%D1%8E-%D0%B8%D0%BC%D0%B5%D1%8E%D1%89%D0%B5%D0%B5-%D0%BF%D0%BE%D0%B4%D0%BC%D0%B5%D0%BD%D1%8E-%D0%B2-python - Как зациклить меню, имеющее подменю в python?
# https://docs.python.org/3/tutorial/venv.html - Creating Virtual Environments.
# https://www.youtube.com/watch?v=A1p7bEtTlxc&t=4s - Как сделать меню для Телеграм Бота на Python.
Task:
Добавить информацию в базу данных в телеграм бот.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyPsqlTelebot.py
root@kvmubuntu:~# cat pyPsqlTelebot.py
import psycopg2
import telebot
from telebot import types
from telebot.types import Message
TOKEN='tkey'
bot = telebot.TeleBot(TOKEN)
DATABASE_URL = "postgres://tuser:tpassword@tipubuntu:5432/tbase2"
def connect_to_db():
conn = psycopg2.connect(DATABASE_URL, sslmode='require')
return conn
def insert_data(words, translate):
conn = connect_to_db()
cursor = conn.cursor()
query = "INSERT INTO Dictionary (words, translate) VALUES (%s, %s)"
cursor.execute(query, (words, translate))
conn.commit()
cursor.close()
conn.close()
@bot.message_handler(commands=['start'])
def handle_start(message: Message):
words = "Текст4"
translate = "Text4"
insert_data(words, translate)
bot.reply_to(message, "Add Info in Doctionary.")
if __name__ == '__main__':
bot.polling(none_stop=True, interval=0)
root@kvmubuntu:~# python3 pyPsqlTelebot.py
- /Start
- Add Info in Doctionary.
root@kvmubuntu:~# psql -U tuser -d tbase2 -h tipubuntu
tbase2=# select * from Dictionary;
id | words | translate
----+------------------------+-------------------
1 | Внутреннее соединение | Inner join
2 | Доступ | Available
3 | Полное соединение | Cross Join
4 | Декартово произведение | Cartesian product
5 | Текст4 | Text4
(5 rows)
Task:
Перенести все логины и пароли в отдельный файл.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim .env
root@kvmubuntu:~# cat .env
TOKEN='tkey'
DB_URL='postgres://tuser:tpassword@tipubuntu:5432/tbase2'
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
translate==3.6.1
pyTelegramBotAPI==4.21.0
googletrans==3.1.0a0
langdetect==1.0.9
psycopg2-binary==2.9.9
python-dotenv==1.0.1
python-decouple==3.8
root@kvmubuntu:~# vim .env
root@kvmubuntu:~# cat .env
TOKEN='tkey'
DB_URL='postgres://tuser:tpassword@tipubuntu:5432/tbase2'
Task:
Применить в Телеграм Бот программ Переводчик.
# Разработка Телеграм ботов.
Decision:
root@kvmubuntu:~# vim pyTranslatorMenuTelebotPsql.py
root@kvmubuntu:~# cat pyTranslatorMenuTelebotPsql.py
import psycopg2
import telebot
from telebot import types
from telebot.types import Message
from googletrans import Translator
from langdetect import detect
from decouple import config
from contextlib import closing
bot = telebot.TeleBot(config('TOKEN'))
translator = Translator()
ru_letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
en_letters = "abcdefghijklmnopqrstuvwxyz"
DATABASE_URL = config('DB_URL')
def connect_to_db():
conn = psycopg2.connect(DATABASE_URL, sslmode='require')
return conn
def insert_dictionary(words, translate):
conn = connect_to_db()
cursor = conn.cursor()
query = "INSERT INTO Dictionary (words, translate) VALUES (%s, %s)"
cursor.execute(query, (words, translate))
conn.commit()
cursor.close()
conn.close()
def insert_terms(words_id, description, translate):
conn = connect_to_db()
cursor = conn.cursor()
query = "INSERT INTO Terms (words_id, description, translate) VALUES (%s, %s, %s)"
cursor.execute(query, (words_id, description, translate))
conn.commit()
cursor.close()
conn.close()
@bot.message_handler(commands=['start'])
def start(message):
message_user = f"Привет, <b>{message.from_user.first_name.title()}</b>! Я тестовый бот.\n" \
f"1. Чем полезен бот\n" \
f"2. Для тех кто хочет поддержать проект"
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
but1 = types.KeyboardButton(text="Чем полезен бот ?")
but2 = types.KeyboardButton(text="Поддержать проект")
markup.add(but1, but2)
bot.send_message(message.from_user.id, message_user, reply_markup=markup, parse_mode='html')
bot.register_next_step_handler(message, funcs_bot)
bot.register_next_step_handler(message, donat_bot)
def funcs_bot(message):
if message.text == "Чем полезен бот ?":
message_user = "Функции бота:\n" \
"1. Переводчик\n" \
"2. Дополнить словарь\n" \
"3. Словарь\n" \
"4. Термины\n" \
"5. Дополнить описание"
key = types.ReplyKeyboardMarkup(resize_keyboard=True)
#button = types.InlineKeyboardButton(text='Мое портфолио', url="http://dato138it.ru")
but1 = types.KeyboardButton("Переводчик")
but2 = types.KeyboardButton("Дополнить словарь")
but3 = types.KeyboardButton("Словарь")
but4 = types.KeyboardButton("Термины")
but5 = types.KeyboardButton("Дополнить описание")
key.add(but1, but2, but3, but4, but5)
bot.send_message(message.from_user.id, message_user, reply_markup=key, parse_mode='html')
#bot.register_next_step_handler(message, funcs_mess)
def donat_bot(message):
if message.text == "Поддержать проект":
message_users = f"<b>Приветствую, уважаемый {message.from_user.first_name.title()}</b>, вы перешли в отдел поддержки нашего проекта\n" \
f"Мы будем благодарны материальной поддержки от вас на:\n" \
f"<b>1. ЮMoney</b>\n" \
f"<b>2. Альфа банк</b>"
key = types.InlineKeyboardMarkup()
but1 = types.InlineKeyboardButton(text="ЮMoney", url="http://dato138it.ru")
but2 = types.InlineKeyboardButton(text="Альфа банк", url="http://dato138it.ru")
key.add(but1, but2)
bot.send_message(message.from_user.id, message_users, reply_markup=key, parse_mode='html')
bot.register_next_step_handler(message, funcs_bot)
@bot.message_handler(content_types=['text'])
def funcs_mess(message):
if message.text == 'Переводчик':
bot.send_message(message.chat.id, 'Напишите сообщение, которое нужно перевести:')
bot.register_next_step_handler(message, translator_mess)
elif message.text == 'Дополнить словарь':
if message.from_user.id == int(config('ADMIN_ID')):
bot.send_message(message.chat.id, 'Напишите 2 сообщения (предложение и его перевод), которые нужно добавить в словарь:')
bot.register_next_step_handler(message, AddDictionary_mess1)
else:
bot.send_message(message.chat.id, 'У вас нет прав на добавление и удаление предложений из базы')
elif message.text == 'Дополнить описание':
if message.from_user.id == int(config('ADMIN_ID')):
bot.send_message(message.chat.id, 'Напишите 3 сообщения (id - в словаре можно увидеть id, описание и его перевод), которые нужно добавить в словарь:')
bot.register_next_step_handler(message, AddTerms_mess1)
else:
bot.send_message(message.chat.id, 'У вас нет прав на добавление и удаление предложений из базы')
elif message.text == 'Словарь':
bot.send_message(message.chat.id, 'Вывожу список слов:')
#bot.register_next_step_handler(message, dictionary_mess)
with closing(connect_to_db()) as conn:
with conn.cursor() as cursor:
cursor.execute('select * from Dictionary;')
for row in cursor:
bot.send_message(message.chat.id, "| "+str(row[0])+" | "+row[1]+" | "+row[2]+" |")
bot.reply_to(message, "Selected to the dictionary.")
elif message.text == 'Термины':
bot.send_message(message.chat.id, 'Вывожу список терминов:')
with closing(connect_to_db()) as conn:
with conn.cursor() as cursor:
cursor.execute('select words, Dictionary.translate, description, Terms.translate from Dictionary inner join Terms on Dictionary.id = Terms.words_id;')
for row in cursor:
bot.send_message(message.chat.id, " | "+str(row[0])+" | "+row[1]+" | "+row[2]+" | "+row[3]+" | ")
bot.reply_to(message, "Selected to the terms.")
else:
bot.send_message(message.chat.id, 'Я тебя не понимаю')
def translator_mess(message):
cmd1 = message.text
if cmd1[0].lower() in ru_letters:
translation = translator.translate(cmd1, src=detect(cmd1), dest='en').text
bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
elif cmd1[0].lower() in en_letters:
translation = translator.translate(cmd1, src=detect(cmd1), dest='ru').text
bot.send_message(message.chat.id, translation.encode('utf-8', 'replace').decode())
else:
bot.send_message(message.chat.id, 'Я тебя не понимаю')
def AddDictionary_mess1(message):
global cmd2
cmd2 = message.text
bot.register_next_step_handler(message, AddDictionary_mess2)
def AddDictionary_mess2(message):
global cmd3
cmd3 = message.text
insert_dictionary(cmd2, cmd3)
bot.reply_to(message, "Info added to the dictionary.")
def AddTerms_mess1(message):
global cmd4
cmd4 = message.text
bot.register_next_step_handler(message, AddTerms_mess2)
def AddTerms_mess2(message):
global cmd5
cmd5 = message.text
bot.register_next_step_handler(message, AddTerms_mess3)
def AddTerms_mess3(message):
global cmd6
cmd6 = message.text
insert_terms(cmd4, cmd5, cmd6)
bot.reply_to(message, "Info added to the terms.")
if __name__ == '__main__':
bot.polling(none_stop=True, interval=0)
root@kvmubuntu:~# python3 py.py
- Чем полезен бот ?
- Функции бота:
1. Переводчик
2. Дополнить словарь
- Дополнить словарь
- Напишите 2 сообщения (предложение и его перевод), которые нужно добавить в словарь:
- new
новый
- Info added to the dictionary.
- Словарь
- Вывожу словарь:
| Inner join | Возвращаются только те строки, где ключевые значения совпадают в обеих таблицах | Only those rows are returned where the key values match in both tables |
| Cross Join | Позволяет получить декартово произведение нескольких таблиц. особенно полезен, когда между таблицами нет определенной связи, и вам нужно создать полную комбинацию записей из каждой таблицы | - |
| Cartesian product | Результат соединения строки из первой таблицы с каждой строкой из второй таблицы | - |
Selected to the dictionary.
root@kvmubuntu:~# psql -U tuser -d tbase2 -h tipubuntu
tbase2=# select * from Dictionary;
...
49 | new | новый
tbase2=# TRUNCATE Terms, Dictionary CASCADE;
Source:
# https://stackoverflow.com/questions/75900203/how-do-i-connect-my-telegram-bot-telebot-to-postgresql-url - How do I connect my telegram bot (telebot) to PostgreSQL url.
# https://pythonru.com/osnovy/globalnye-peremennye-python - Правила использования global.
# https://ru.stackoverflow.com/questions/1103332/Авторизация-в-телеграмм-боте-на-python - Авторизация в телеграмм боте на Python.
# https://otvet.mail.ru/question/219454196 - Python. Telegram bot api Как из Username получить user_id.
# https://stackoverflow.com/questions/13223820/postgresql-delete-all-content - PostgreSQL удаляет все содержимое.
Task:
Настройка службы бота.
# Администрирование локальных, виртуальных и облачных серверов.
Decision:
root@kvmubuntu:~# vim /etc/systemd/system/dato38itbot.service
root@kvmubuntu:~# cat /etc/systemd/system/dato38itbot.service
[Unit]
Description=Telegram dato38it-bot
After=network.target
[Service]
User=tuser
Group=tuser
WorkingDirectory=/home/tuser/dato38itbot/
VIRTUAL_ENV=/home/tuser/dato38itbot/telegaenv
Environment=PATH=$VIRTUAL_ENV/bin:$PATH
ExecStart=/home/tuser/dato38itbot/telegaenv/bin/python /home/tuser/dato38itbot/main.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
root@kvmubuntu:~# systemctl daemon-reload
root@kvmubuntu:~# systemctl enable dato38itbot.service
root@kvmubuntu:~# systemctl start dato38itbot.service
root@kvmubuntu:~# systemctl status dato38itbot.service
Source:
# https://thecode.media/systemctl/ - Готовим файл для работы службы.
# https://synay.net/vps/preconfigured/own-telegram-bot-server-debian-12 - Создадим файл службы, чтобы запускать бот автоматически.
# https://gist.github.com/ricferr/90583f608f0b0ae9c3cf6833be04ab85 - How to create a systemd service for python script with virtualenv.
2023-11-01 - 2024-05-30: Информационно-аналитический центр поддержки ГАС правосудие, Иркутск. Должность: Инженер 2 категории / Системный администратор. Дополнительная информация: Обязанности - Установка, обновление и контроль состояния программного обеспечения на объектах автоматизации, Введение эксплуатационной документации, Поддержка функционирования серверов и сервисов СУБД, Техническая поддержка пользователей. Навыки - Виртуализация, Windows, Sql. Достижения: Разработал скрипты, которые автоматизируют процессы резервного копирования базы данных перед обновлением программы ГАС правосудие.
Show
# Администрирование локальных, виртуальных и облачных серверов.
# Администрирование базы данных.
Task:
Администрирование локальных, виртуальных и облачных серверов. Ошибка после ввода пароля в программе у всех пользователей - "Не отвечает программа"
ВНЕШНЯЯБАЗА2.exe не работает
На одном из объектов автоматизации второй раз за месяц происходит процесс переполнения внешней базы документооборота C:\ПУТЬКБАЗЕ\ВНЕШНЯЯБАЗА1.GDB. База данных увеличивается в размерах с 1.8 Гб до 115Гб за три дня. После проведения инженером филиала процедуры Бэкап/Рестор внешняя база возвращается к прежнему размеру. Данная проблема наблюдается второй раз за месяц с периодичностью в две недели.
Decision:
Отключить службы Firebird, Сделать backup/restore базы ВНЕШНЯЯБАЗА1.GDB, запустив скрипты
1. backup.bat
2. restore.bat
после чего сохранится файл ВНЕШНЯЯБАЗА1.GDB в КУДАСОХРАНЯТЬИЗМЕНЕНИЯ. Файл заменить на файл в сервере, который занимал 120 гб. Посмотреть лог файлы:
1. retranslator.log
2. retranslator_2022-01-11_15-23-40-694.log
3. retranslator_2022-01-24_16-36-09-627.log
4. retranslator_2022-01-24_16-36-16-806.log
Decision:
$ cat backup.bat
@ echo on
SET ISC_USER=ИМЯПОЛЬЗОВАТЕЛЯБАЗЫ
SET ISC_PASSWORD=ПАРОЛЬКБАЗЕ
SET dbpath=localhost:C:\ПУТЬКБАЗЕ\ВНЕШНЯЯБАЗА1.GDB
SET fbpath=C:\ПУТЬFIREBIRD\Firebird_1_5\bin\
SET bbpath=C:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\
"%fbpath%gfix" -shut -force 5 "%dbpath%"
"%fbpath%gbak" -b -v -g -y "%bbpath%%date%.log" "%dbpath%" "%bbpath%ВНЕШНЯЯБАЗА1_%date%.gbk"
@ pause
$ cat restore.bat
@ echo on
SET ISC_USER=ИМЯПОЛЬЗОВАТЕЛЯБАЗЫ
SET ISC_PASSWORD=ПАРОЛЬКБАЗЕ
SET fbpath=C:\ПУТЬFIREBIRD\Firebird_1_5\bin\
SET bbpath=localhost:C:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\
"%fbpath%gbak.exe" -c -v -r -y "c:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\%date%_fix.log" "c:\КУДАСОХРАНЯТЬИЗМЕНЕНИЯ\ВНЕШНЯЯБАЗА1_backup.gbk" "%bbpath%ВНЕШНЯЯБАЗА1_fix.GDB"
@ pause
$ cat retranslator.log
...
INF|09.12.2022 16:27:36 Сообщения в очереди "viv.client.КОДОА.1" отсутствуют
INF|09.12.2022 16:32:39 Отправка запроса id="ИДЕНТИФИКАТОРПРОЦЕССА1", type_id=ИДЕНТИФИКАТОРТИП ...
ERR|09.12.2022 16:33:37 Out of memory.
E.ClassName=EOutOfMemory
Sender.ClassName=TWorkThread
...
INF|11.12.2022 22:28:27 Отправка запроса id="ИДЕНТИФИКАТОРПРОЦЕССА2", type_id=ИДЕНТИФИКАТОРТИП ...
ERR|11.12.2022 22:28:53 При отправке запроса id="ИДЕНТИФИКАТОРПРОЦЕССА2" произошла неустранимая ошибка чтения из БД, запрос отклонён. Текст ошибки:
Unsuccessful execution caused by a system error that precludes
successful execution of subsequent statements.
I/O error for file "C:\ПУТЬКБАЗЕ\ВНЕШНЯЯБАЗА1.GDB".
Error while trying to write to file.
Недостаточно места на диске.
...
Decision:
при обработке одного запроса id="ИДЕНТИФИКАТОРПРОЦЕССА1", type_id=ИДЕНТИФИКАТОРТИП происходит нехватка памяти, что далее приводит к сбою функционирования. Вероятно в запросе большое вложение. Для дальнейшего анализа смотрим БД ВНЕШНЯЯБАЗА2.gdb, в которой покажет технические ошибки состояния базы ВНЕШНЯЯБАЗА2.gdb.
Для их устранения также сделать бэкап/ресторе БД ВНЕШНЯЯБАЗА2.gdb. Далее понаблюдать за ситуацией с размером БД ВНЕШНЯЯБАЗА2.GDB, чтобы он весил не больше 19 Гб.
Task:
Администрирование базы данных. После отключения света в здании служба OracleServiceНАЗВАНИЕСИСТЕМЫ не запускается. При подключении к БСР клиента на сервере выходит следующая ошибка:
ОШИБКА ORA-01033. ORACLE INITIALIZATION OR SHUTDOWN IN PROGRESS
Не помогает перезапуск службы ORACLE и перезагрузка сервера.
Также пробовали подключиться и настроить базу в командной строке через sql plus. После ввода команды выдает ошибку:
C:\>sqlplus
Enter user-name: СОКРАЩЕННОЕОЕИМЯПОЛЬЗОВАТЕЛЯ as ПОЛНОЕИМЯПОЛЬЗОВАТЕЛЯ
SQL> alter pluggable database all open;
alter pluggable database all open
*
ERROR at line 1:
ORA-00940: invalid ALTER command
Нужно сделать рестарт базы
Decision:
C:\>sqlplus /nolog
SQL> connect СОКРАЩЕННОЕОЕИМЯПОЛЬЗОВАТЕЛЯ/pass@НАЗВАНИЕСИСТЕМЫ as ПОЛНОЕИМЯПОЛЬЗОВАТЕЛЯ
ERROR:
ORA-01017: invalid username/password; logon denied
SQL> conn СОКРАЩЕННОЕОЕИМЯПОЛЬЗОВАТЕЛЯ/oracle@НАЗВАНИЕСИСТЕМЫ as ПОЛНОЕИМЯПОЛЬЗОВАТЕЛЯ
SQL> startup mount
ORA-01081: cannot start already-running ORACLE - shut it down first
SQL> shutdown immediate
ORA-01109: database not open
Database dismounted.
ORACLE instance shut down.
SQL> startup mount
SQL> col file format a5;
SQL> col name format a20;
SQL> col status format a10;
SQL> select file#,name,status from v$datafile;
FILE# NAME STATUS
---------- -------------------- ----------
1 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM01.DBF
2 C:\ORACLE\ORADATA\GA ONLINE
S\UNDOTBS01.DBF
3 C:\ORACLE\ORADATA\GA ONLINE
S\CWMLITE01.DBF
4 C:\ORACLE\ORADATA\GA ONLINE
S\DRSYS01.DBF
FILE# NAME STATUS
---------- -------------------- ----------
5 C:\ORACLE\ORADATA\GA ONLINE
S\EXAMPLE01.DBF
6 C:\ORACLE\ORADATA\GA ONLINE
S\INDX01.DBF
7 C:\ORACLE\ORADATA\GA ONLINE
S\ODM01.DBF
8 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME STATUS
---------- -------------------- ----------
S\TOOLS01.DBF
9 C:\ORACLE\ORADATA\GA ONLINE
S\USERS01.DBF
10 C:\ORACLE\ORADATA\GA ONLINE
S\XDB01.DBF
11 C:\ORACLE\ORADATA\GA ONLINE
S\TBLS_BSR.ORA
FILE# NAME STATUS
---------- -------------------- ----------
12 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_D.DAT
13 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_I.DAT
14 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_O.DAT
15 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_D.DAT
FILE# NAME STATUS
---------- -------------------- ----------
16 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_I.DAT
17 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_O.DAT
18 C:\ORACLE\ORADATA\GA ONLINE
S\ARCH_D.DAT
19 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME STATUS
---------- -------------------- ----------
S\ARCH_I.DAT
20 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_D.DAT
21 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_I.DAT
22 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_O.DAT
FILE# NAME STATUS
---------- -------------------- ----------
23 C:\ORACLE\ORADATA\GA ONLINE
S\.ORA
24 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM02.ORA
25 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM03.ORA
25 rows selected.
SQL> recover datafile 1;
SQL> select file#,name,status from v$datafile;
FILE# NAME STATUS
---------- -------------------- ----------
1 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM01.DBF
2 C:\ORACLE\ORADATA\GA ONLINE
S\UNDOTBS01.DBF
3 C:\ORACLE\ORADATA\GA ONLINE
S\CWMLITE01.DBF
4 C:\ORACLE\ORADATA\GA ONLINE
S\DRSYS01.DBF
FILE# NAME STATUS
---------- -------------------- ----------
5 C:\ORACLE\ORADATA\GA ONLINE
S\EXAMPLE01.DBF
6 C:\ORACLE\ORADATA\GA ONLINE
S\INDX01.DBF
7 C:\ORACLE\ORADATA\GA ONLINE
S\ODM01.DBF
8 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME STATUS
---------- -------------------- ----------
S\TOOLS01.DBF
9 C:\ORACLE\ORADATA\GA ONLINE
S\USERS01.DBF
10 C:\ORACLE\ORADATA\GA ONLINE
S\XDB01.DBF
11 C:\ORACLE\ORADATA\GA ONLINE
S\TBLS_BSR.ORA
FILE# NAME STATUS
---------- -------------------- ----------
12 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_D.DAT
13 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_I.DAT
14 C:\ORACLE\ORADATA\GA ONLINE
S\DELO_O.DAT
15 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_D.DAT
FILE# NAME STATUS
---------- -------------------- ----------
16 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_I.DAT
17 C:\ORACLE\ORADATA\GA ONLINE
S\KADR_O.DAT
18 C:\ORACLE\ORADATA\GA ONLINE
S\ARCH_D.DAT
19 C:\ORACLE\ORADATA\GA ONLINE
FILE# NAME STATUS
---------- -------------------- ----------
S\ARCH_I.DAT
20 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_D.DAT
21 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_I.DAT
22 C:\ORACLE\ORADATA\GA ONLINE
S\EDS_O.DAT
FILE# NAME STATUS
---------- -------------------- ----------
23 C:\ORACLE\ORADATA\GA ONLINE
S\.ORA
24 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM02.ORA
25 C:\ORACLE\ORADATA\GA SYSTEM
S\SYSTEM03.ORA
25 rows selected.
SQL> alter database open;
Task:
Администрирование базы данных. В ПОДСИСТЕМА не грузятся на сайт 200 дел. Смотрим лог файл:
$ cat importer.log.xml
...
<Error ReadableDateTime="23.01.2023 12:51:31" FileTimeUtc="133189230912753002">
<Message>Îøèáêà ïðè âûïîëíåíèè çàïðîñà:
BEGIN BSR.PK_BSR.CTX_Sinxronize; END;
Òåêñò îøèáêè:
ORA-20000: Oracle Text error:
DRG-50857: oracle error in dreii0fsh
ORA-01653: unable to extend table BSR.DR$CTX_SRH$I by 8192 in tablespace SYSTEM
ORA-06512: at "CTXSYS.DRUE", line 157
ORA-06512: at "CTXSYS.CTX_DDL", line 1328
ORA-06512: at "BSR.PK_BSR", line 313
ORA-06512: at line 1
</Message>
<Details>System.Exception: Îøèáêà ïðè âûïîëíåíèè çàïðîñà:
BEGIN BSR.PK_BSR.CTX_Sinxronize; END;
Òåêñò îøèáêè:
ORA-20000: Oracle Text error:
DRG-50857: oracle error in dreii0fsh
ORA-01653: unable to extend table BSR.DR$CTX_SRH$I by 8192 in tablespace SYSTEM
ORA-06512: at "CTXSYS.DRUE", line 157
ORA-06512: at "CTXSYS.CTX_DDL", line 1328
ORA-06512: at "BSR.PK_BSR", line 313
ORA-06512: at line 1
â ImpFunktions.Database.DbaseIns(String cmdString)
â ImpFunktions.Importer.synchronize()</Details>
</Error>
пуск - все программы - oracle orahome92 - enterprize manager console - ок - databases - gas - system - oracle - storage - tablespaces
Проблема из за того, что у Вас закончилось свободное оракловое ТП SYSTEM и Tbls_bsr, которые надо увеличить вручную путем добавления DATA файла. "Флаг" в чек боксе Авторасширение не поможет.
Decision:
Обязательно делайте бэкап перед любыми действиями с базой. Дважды щелкните ЛКМ табличное пространство SYSTEM и добавьте в него второй файл. Oracle автоматически подставит расширение ORA. Исправьте его на DBF перед сохранением.
system - add datafile - name - SYSTEM04.ORA - file size - 4096MB - storage - automatically extend datafile when full - increment - 10240KB - value - 32767MB - ok - перезапустить службу BSRImport - В админке обновлять авоматический импорт из СДП
Task:
Администрирование базы данных. Можно сделать это в командной строке, добавив дополнительный файл данных к табличному пространству и изменив размер текущего файла данных, Некоторые версии Oracle не позволяют такого!
Decision:
ALTER TABLESPACE USERS ADD DATAFILE '/u01/oradata/orcl/users02.dbf' size 25m;
ALTER DATABASE DATAFILE '/u01/oradata/orcl/users01.dbf' resize 50M;
Необходимо добавить новый датафайл к табличному пространству SYSTEM.
2022-06-01 - 2022-11-01: Yandex Практикум, Иркутск. Должность: Инженер облачных сервисов - Дополнительное образование. Дополнительная информация: Навыки - Html, Виртуализация, Linux, Sql, Bash, Clouds, Docker, Python. Достижения: В рамках программы "Инженер облачных сервисов Yandex" защитил практические работы по темам "Хранение и анализ данных", "Devops и автоматизация", "Serverless" и "Безопасность".
Show
# развернул пять кластеров баз данных MySQL, PostgreSQL, MongoDB, ClickHouse и Ydb
# добавил данные из файлов в БД ClickHouse для анализа прогноза за всю историю наблюдений за последние несколько лет с помощью SQL-запросов
# добавил данные из тестового приложения для подключения к БД YDB и запуска тестового приложения, чтобы создать в ней несколько таблиц с данными о популярных сериалах
# реализовал систему хранения рентгеновских снимков для клиники
# развернул кластер Hadoop с помощью сервиса Yandex Data Proc
# поднял кластер Kubernetes в Yandex Cloud
# развернул приложение веб-сервер NGINX c Балансировкой нагрузки и Автомасштабированием в Yandex Managed Kubernetes
# проверил на отказоустойчивость по основным сценариям сбоев
# разработал навык Алисы, которая повторяет всё, что вы ему напишете с сохранением фраз в новом файле в бакете
# разработал функцию для проверки доступности сайта ya.ru, которая будет измерять время ответа, передавать в БД PostgreSQL результаты работы функции, запускать триггер-таймер для регулярного опроса сайта ya.ru
# с помощью REST API получил до 50 результатов проверки из БД,
# реализовал проекты, которые позволят пользователям конвертировать видеофайлы в GIF и конвертировать длинные ссылки в короткие
# реализовал права на управление сервисным аккаунтом
# организовал защищённый канал настроив IPSec VPN-туннель между двумя VPN-шлюзами в ВМ с помощью демона strongSwan
# реализовал для домена втоматический выпуск сертификата с помощью Certificate Manager
Task:
Хранение и анализ данных в Yandex Cloud
Task:
Создание бакетов и загрузка объектов
Потренируемся работать с объектным хранилищем на практике. Представьте, что вы создаёте облачную систему хранения рентгеновских снимков для крупной клиники.
Рентгеновские снимки — это неструктурированные данные, которые нельзя изменять, нужно надежно хранить и легко находить.
Загруженные файлы будут скачивать нечасто. Также важно предоставлять доступ к файлам другим клиникам (это пригодится, если пациента переводят или врачу надо посоветоваться с коллегами). Объектное хранилище — подходящее решение задачи.
Decision:
Выберите на стартовой странице консоли управления сервис Object Storage.
Давайте создадим бакет для рентгеновских снимков.
Нажмите кнопку Создать бакет. Откроется окно с основными параметрами:
Имя. Придумайте его с учетом правил. Обратите внимание, что дать бакету имя hospital не получится. Имена бакетов во всем Yandex Object Storage уникальны — назвать два бакета одинаково нельзя даже в разных облаках. Помните об этом, если будете создавать бакеты автоматически.
Макс. размер. У вас есть два варианта: Выбрать опцию Без ограничения. Размер бакета будет увеличиваться, сколько бы объектов в него ни помещали. Указать максимальный размер. Это убережёт вас от финансовых потерь, если что-то пойдёт не так и в бакет загрузится слишком много объектов.
Другие опции. Далее для всех типов операций оставьте ограниченный доступ (публичный позволяет выполнять операции всем пользователям интернета), выберите стандартный класс хранилища и нажмите кнопку Создать бакет.
На странице объектного хранилища появился пустой бакет. Мы приготовили два рентгеновских снимка: image01.dat и image02.dat. Файлы можно загрузить в бакет с помощью: консоли управления; приложений; S3-совместимого HTTP API; HTML-форм на сайте.
Разберём два способа: ручную загрузку через консоль управления и автоматическую с помощью утилиты S3cmd.
Для загрузки файла через консоль управления выберите созданный бакет и в открывшемся окне нажмите кнопку Загрузить объекты.
Выберем файл image01.dat. В появившейся форме нажмите кнопку Загрузить — и вы увидите, что файл оказался в хранилище.
Загрузите второй файл с помощью утилиты S3cmd — консольного клиента для Linux и MacOS, предназначенного для работы с S3-совместимым HTTP API. Для работы в Windows используйте один из вариантов: установите другой консольный клиент для объектных хранилищ, например AWS CLI; установите подсистему Linux на Windows с помощью утилиты WSL (Windows Subsystem for Linux) и работайте с S3cmd в ней; создайте в облаке виртуальную машину с Ubuntu и работайте с S3cmd в ней. Для загрузки файла-примера в виртуальную машину воспользуйтесь командой:
$ wget "https://disk.yandex.ru/i/2UlugGkurhcxWw" -O image02.dat
Установите S3cmd (в Ubuntu, например, это делается с помощью команды sudo apt-get install s3cmd). Теперь настройте S3cmd для работы с Yandex Object Storage:
$ s3cmd --configure
Инструкции о настройке клиента вы найдете в документации.
После ввода параметров утилита попытается установить соединение с объектным хранилищем и в случае успеха покажет такое сообщение: Success. Your access key and secret key worked fine :-)
Загрузим в бакет второй файл (image02.dat) и затем получим список хранящихся в бакете объектов:
s3cmd put <путь к второму файлу>/image02.dat s3://<имя бакета>
s3cmd ls s3://<имя бакета>
Вернемся в консоль управления.
Мы видим, что класс хранилища у обоих объектов — стандартное. Напомним: стандартное хранилище подходит для данных, к которым обращаются часто, а тариф за размещение данных в нем примерно в два раза выше, чем в холодном хранилище.
Спустя несколько недель после того, как рентгеновский снимок сделан, к нему будут редко обращаться (если вообще будут), потому что пациент, скорее всего, выздоровеет.
Чтобы оптимизировать затраты на хранение данных, настроим жизненный цикл объектов в бакете. Создадим правило, согласно которому через 30 дней после загрузки объектов в бакет класс их хранилища будет автоматически меняться со стандартного на холодное.
Перейдите на вкладку Жизненный цикл и нажмите кнопку Настроить. Задайте произвольное описание. В поле Префикс укажите Все объекты. Выберите тип операции Transition. В качестве Условия срабатывания правила задайте Точную дату или Количество дней. В первом случае правило сработает в 00:00 установленной даты. Во втором — через указанное количество дней после загрузки объекта в бакет.
Если понадобится настроить автоматическое удаление объектов, выберите тип операции Expiration. Нажмите кнопку Сохранить.
Представим теперь, что объекты в хранилище — это оцифрованные рентгеновские снимки пациента Петрова. Первый из них (image01.dat) сделали несколько месяцев назад в ходе профосмотра, а второй (image02.dat) — вчера, после того как Петров обратился к врачу с жалобой на недомогание. В обоих случаях на снимках не увидели патологий.
Опишите с помощью пользовательских метаданных эти снимки, и позже вы быстро найдете их среди множества объектов в бакете.
С помощью утилиты S3cmd задайте для загруженных объектов метаданные с фамилией пациента (x-amz-meta-patient:petrov) и с результатами обследования (x-amz-meta-status:ok):
s3cmd modify --add-header=x-amz-meta-patient:petrov --add-header=x-amz-meta-status:ok s3://hospital/image01.dat s3://hospital/image02.dat
Выведите на экран информацию об этих объектах, чтобы проверить, что получилось:
s3cmd info s3://hospital/image01.dat s3://hospital/image02.dat
В результате вы должны увидеть информацию об объектах в бакете.
s3://hospital/image01.dat (object):
File size: 33
Last mod: Thu, 04 Mar 2021 22:05:31 GMT
MIME type: application/x-www-form-urlencoded
Storage: STANDARD
MD5 sum: 6f6d5a1cb79839e523582ed8810a42fd
SSE: none
Policy: none
CORS: none
x-amz-meta-patient: petrov
x-amz-meta-status: ok
s3://hospital/image02.dat (object):
File size: 16
Last mod: Thu, 04 Mar 2021 23:11:27 GMT
MIME type: text/plain
Storage: STANDARD
MD5 sum: 0366a1d19e584ce79d5c05ddedc69310
SSE: none
Policy: none
CORS: none
x-amz-meta-patient: petrov
x-amz-meta-status: ok
Предположим, Петров чувствует себя хуже. Судя по анализам, он действительно болен. Лечащий врач решает проконсультироваться с более опытной коллегой Ивановой из профильной клиники. Объекты в бакете недоступны для внешних пользователей, поскольку при его создании мы ограничили доступ. Чтобы Иванова увидела рентгеновский снимок Петрова, отправим ей временную ссылку на объект image02.dat.
Для этого в консоли управления кликните на объект и в открывшемся окне информации об объекте нажмите кнопку Получить ссылку. Укажите время жизни ссылки в часах или днях.
Можно поделиться ссылкой или использовать ее в любом сервисе для доступа к файлу.
После консультации Иванова поставила Петрову правильный диагноз: вирусная пневмония (код J12 по Международной классификации болезней). Вам осталось исправить метаданные объекта image02.dat. Замените значение метаданных с результатами обследования с ok на J12 самостоятельно.
Decision:
$ wget "https://disk.yandex.ru/i/2UlugGkurhcxWw" -O image02.dat
$ wget "https://disk.yandex.ru/i/qpl0T4u-nWPgiw" -O image01.dat
$ ls *.dat
image01.dat image02.dat
$ sudo apt-get install s3cmd
$ s3cmd --configure
$ s3cmd ls
2023-12-06 02:37 s3://klinika138
$ s3cmd put /YOUR-DIR/image02.dat s3://klinika138
$ s3cmd modify \
--add-header=x-amz-meta-patient:petrov \
--add-header=x-amz-meta-status:ok \
s3://klinika138/image01.dat \
s3://klinika138/image02.dat
$ s3cmd info \
s3://klinika138/image01.dat \
s3://klinika138/image02.dat
Task:
Хранение статических веб-сайтов в Object Storage
Представьте, что вам нужно выбрать оптимальный хостинг для сайта клиники. Главные критерии: отказоустойчивый, недорогой и простой в обслуживании.
Один из вариантов решения такой задачи — использовать объектное хранилище. Вы можете, не настраивая никаких серверов, просто загрузить HTML-файлы, скрипты, стили и другие файлы в хранилище. Пользователи будут открывать в браузере ваш сайт, а по сути — скачивать файлы прямо из бакета.
Важно понимать, что этот вариант подойдет только для полностью статических сайтов. Иными словами, сайт должен быть сделан с помощью клиентских технологий (HTML, CSS и JavaScript) и не требовать запуска чего-либо на стороне веб-сервера.
Предположим, что сайт нашей клиники как раз такой — полностью статический. Опубликуйте его с помощью объектного хранилища. Прежде всего для него нужно создать бакет:
Decision
Обратите внимание на несколько особенностей: Если вы планируете использовать собственный домен (например www.example.com), то присвойте бакету точно такое же имя. Откройте публичный доступ на чтение объектов. Это позволит пользователям интернета скачивать объекты из бакета и просматривать сайт в браузере.
Задайте необходимые настройки и нажмите кнопку Создать бакет.
Теперь загрузите в бакет файлы сайта (например, этот и этот) любым удобным способом.
Чтобы настроить хостинг, перейдите на страницу бакета в консоли управления. Выберите вкладку Веб-сайт на левой панели и включите опцию Хостинг.
Укажите файл с главной страницей сайта (как правило, это index.html), а поле со страницей ошибки можно не заполнять.
Сохраните настройки, и сайт станет доступен по адресам:
http(s)://<имя_бакета>.website.yandexcloud.net
http(s)://website.yandexcloud.net/<имя_бакета>
По умолчанию сайт будет доступен только по протоколу HTTP. Для поддержки HTTPS нужно загрузить в объектное хранилище TLS-сертификат. Вам предстоит это сделать в одной из практических работ курса «Безопасность».
Если у вас есть собственный домен и вы хотите опубликовать сайт на нём, то настройте CNAME-запись у DNS-провайдера или на своем DNS-сервере. Например, для домена www.example.com CNAME-запись выглядела бы так: www.example.com CNAME www.example.com.website.yandexcloud.net
В этом случае можно использовать домены не ниже третьего уровня (то есть использовать домен example.com не получится, только www.example.com). Это связано с особенностями обработки CNAME-записей на DNS-хостингах.
Decision:
$ wget "https://disk.yandex.ru/d/KcpMuYBwKjIa6Q" -O index.html
$ wget "https://disk.yandex.ru/i/uTai62_esPSaEw" -O doctor.png
$ s3cmd put /YOUR-DIR/doctor.png s3://www.aibloit.healthcare138
$ s3cmd put /YOUR-DIR/index.html s3://www.aibloit.healthcare138
Task:
Создание кластера базы данных MySQL. Object Storage — удобный и полезный инструмент для хранения данных в облаке. Но для решения практических задач важно не просто хранить данные, но и иметь возможность их изменять и выполнять с ними различные операции (сортировать, группировать, делать выборки и так далее). Для этого используются базы данных. В этой и следующих темах вы научитесь работать с несколькими управляемыми БД. И начнем мы с одной из самых популярных — MySQL.
На этом уроке вы создадите и настроите кластер управляемой БД MySQL, подключитесь к нему, перенесёте данные в облако, познакомитесь с возможностями резервного копирования и мониторинга. Эти навыки пригодятся вам и в других сервисах управляемых БД, поскольку принципы работы в них очень похожи.
Предположим, вы решили добавить в разрабатываемый вами мессенджер новую функциональность. Вы написали микросервис, который позволяет оценивать сообщения в групповых чатах и хранит оценки в БД MySQL. Давайте поместим эту БД в Yandex Cloud.
Decision:
Прежде всего понадобится создать кластер: набор виртуальных машин (ВМ, или хостов), на которых будет развёрнута БД. Это обязательный первый шаг при использовании любого сервиса управляемых БД.
Войдите в консоль управления Yandex Cloud и выберите каталог для кластера. Вверху справа нажмите кнопку Создать ресурс и выберите из выпадающего списка Кластер MySQL.
Откроется страница с основными настройками кластера. Рассмотрим их подробнее.
Базовые параметры. Имя кластера может включать только цифры, прописные и строчные латинские буквы, дефисы.
Поле Описание заполнять необязательно. Оно полезно, если вам нужно создать несколько кластеров для разных целей, чтобы в них было проще ориентироваться.
О том, какое бывает Окружение кластера и чем различаются PRESTABLE и PRODUCTION, мы говорили на одном из предыдущих уроков. Поскольку микросервис только разрабатывается, выберите окружение PRESTABLE.
Версия. В качестве сервера MySQL в Yandex Cloud используется Percona Server версии 5.7 или 8.0. У этих реализаций сервера улучшенная производительность на многоядерных машинах. Если для вас критична стабильность работы микросервиса, выбирайте проверенную временем 5.7. Для нашей задачи подойдёт 8.0: в ней много новых функций, но она ещё не полностью обкатана.
Класс хостов. Следующий шаг — выбор класса хостов, или шаблона ВМ. Хосты кластера будут развёрнуты на базе ВМ Compute Cloud с использованием этого шаблона.
Платформа определяет тип физического процессора (Intel Broadwell или Intel Cascade Lake), а также конфигурации числа ядер виртуального процессора (vCPU) и размера оперативной памяти.
Если тип процессора для вас неважен, выбирайте более современную платформу Intel Cascade Lake. Она предоставляет широкий выбор конфигураций вычислительных ресурсов.
Также на конфигурации влияет тип ВМ, на которой будет развёрнута БД.
Standard — это обычные ВМ с 4 ГБ RAM на ядро vCPU. Это оптимальный баланс между количеством запущенных процессов, быстродействием и потребляемой оперативной памятью.
Memory-optimized — машины с вдвое увеличенным объёмом RAM на каждое ядро. Выбирайте их для высоконагруженных сервисов с повышенными требованиями к кешу.
Burstable — машины, для которых гарантируется использование лишь доли ядра vCPU (5, 20 или 50%) с вероятностью временного повышения вплоть до 100%. Они стоят дешевле и подходят для задач, где не нужен постоянный уровень производительности, т. е. для тестирования или разработки.
Выберем для микросервиса следующий класс хоста: платформа — Intel Cascade Lake; тип — standard; конфигурация вычислительных ресурсов — s2.micro (два ядра vCPU, 8 ГБ RAM).
Хранилище данных. Хранилище БД может быть сетевым или локальным. В первом случае данные находятся на виртуальных дисках в инфраструктуре Yandex Cloud. Локальное хранилище — это диски, которые физически размещаются в серверах хостов БД.
При создании кластера можно выбирать между следующими типами хранилища:
- Стандартное сетевое (network-hdd) — это наиболее экономичный вариант. Выбирайте его, если к скорости записи и чтения нет особых требований.
- Быстрое сетевое (network-ssd) стоит примерно в четыре раза дороже, но при размере хранилища от 100 ГБ работает быстрее стандартного в десять и более раз (чем больше размер, тем заметнее разница в скорости).
- Сетевое на нереплицируемых SSD-дисках (network-ssd-nonreplicated) — использует сетевые SSD-диски с повышенной производительностью, реализованной за счет устранения избыточности. Объём такого хранилища можно увеличивать только с шагом 93 ГБ.
- Быстрое локальное (local-ssd) — самое быстрое и дорогое. Если локальный диск откажет, все сохранённые на нём данные будут потеряны. Чтобы этого избежать, при выборе локального хранилища сервис автоматически создаст отказоустойчивый кластер минимум из трёх хостов.
При создании кластера внимательно выбирайте тип хранилища. Размер хранилища можно будет позже изменить, а тип — нет.
Выберите для кластера стандартное сетевое хранилище network-hdd размером 50 ГБ.
База данных. В этом разделе настроек задаются атрибуты базы: Имя БД, уникальное в рамках кластера, Имя пользователя (владельца БД) и Пароль пользователя.
Сеть. Здесь можно выбрать облачную сеть для кластера и группы безопасности для его сетевого трафика.
Оставьте сеть по умолчанию (default) или выберите сеть, которую создали на предыдущем курсе. Кластер будет доступен для всех ВМ, которые подключены к вашей облачной сети.
Параметры хостов. В этом блоке можно добавить количество хостов, которые будут созданы вместе с кластером, и изменить их параметры. Дополнительные хосты могут понадобиться, например, для репликации БД или снижения нагрузки на хост-мастер.
Для наших целей достаточно кластера из одного хоста. Нажмите значок редактирования параметров хоста и в открывшемся окне выберите опцию Публичный доступ. Это означает, что к хосту можно будет подключиться из интернета, а не только из облачной сети. Остальные параметры оставьте без изменений.
Дополнительные настройки.Здесь можно: указать время Начала резервного копирования и Окна обслуживания. Это пригодится, если вы хотите, чтобы резервное копирование и техобслуживание хостов кластера не совпадали с периодами пиковых нагрузок на БД; разрешить Доступ из DataLens, если вы планируете анализировать в DataLens данные из базы. Подробнее о DataLens вы узнаете на одном из следующих занятий; разрешить Доступ из консоли управления, чтобы выполнять SQL-запросы к БД из консоли управления Yandex Cloud. Отметьте этот пункт: доступ из консоли понадобится нам на следующих практических работах; разрешить Доступ из Data Transfer, чтобы разрешить доступ к кластеру из сервиса Yandex Data Transfer в Serverless-режиме; разрешить Сбор статистики, чтобы воспользоваться инструментом Диагностика производительности в кластере; установить Защиту от удаления, чтобы защитить кластер от непреднамеренного удаления пользователем.
В этом блоке также можно задать настройки БД (например используемую сервером MySQL кодировку при работе с данными и обмене информацией с клиентами). По умолчанию при создании кластера сервис выбирает оптимальные настройки. Изменяйте их, если уверены, что это необходимо.
Настройка завершена. Осталось только нажать кнопку Создать кластер.
Создание кластера займёт несколько минут. Когда он будет готов к работе, его статус на панели Managed Service for MySQL сменится с Creating на Running, а состояние — на Alive.
Статус показывает, что происходит с кластером: Creating — создаётся; Running — работает; Error — не отвечает, возникла проблема; Updating — обновляется; Stopped — остановлен; Unknown — статус неизвестен (так может быть, например, когда кластер не виден из интернета).
Состояние — это показатель доступности кластера: Alive — все хосты кластера работают; Degraded — часть хостов (один или больше) не работает; Dead — все хосты не работают.
Task:
Подключение к БД и добавление данных
Доступ из консоли управления. В кластере, который вы создали, уже есть БД. Она пока пустая. Поскольку при создании кластера вы выбрали в настройках пункт Доступ из консоли управления, в консоли управления Yandex Cloud появилась вкладка с интерфейсом для выполнения SQL-запросов к БД.
Давайте зайдём туда и создадим в БД таблицу для нашего микросервиса.
Decision:
На странице Managed Service for MySQL выберите строку с созданным вами кластером. В панели консоли управления перейдите на вкладку SQL. Вам будет предложено выбрать БД для SQL-запросов и имя пользователя, а также ввести пароль. Все эти атрибуты вы задавали при создании кластера.
Нажмите кнопку Подключиться. Откроется структура БД (сейчас там написано, что данных нет) и окно ввода для SQL-запросов.
Теперь создадим таблицу. Введите в окне ввода следующий запрос и нажмите кнопку Выполнить.
CREATE TABLE IF NOT EXISTS ratings (
rating_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
message_id INT NOT NULL,
rating INT NOT NULL
) ENGINE=INNODB;
Обратите внимание, что в качестве движка в сервисе управляемых БД MySQL используется только InnoDB.
В таблицу можно добавить данные с помощью команды INSERT.
INSERT INTO ratings (user_id,message_id,rating) VALUES (44,368,4);
Чтобы отобразить обновлённую структуру БД, нажмите на имя БД и выберите таблицу ratings.
Наведите указатель на заголовок столбца, чтобы увидеть тип данных в нём.
SQL-запросы через консоль управления Yandex Cloud — нетипичный способ работы с БД. Используйте его для небольших, разовых задач, когда быстрее и проще открыть подключение в браузере. Этот способ не очень удобен: текст запроса и результат его выполнения доступны, только пока вы не закрыли или не перезагрузили страницу в браузере. Конечно, если запрос успешно запущен, то сервис обработает его независимо от состояния консоли управления.
В консоли выводятся только первые 1000 строк результата запроса, даже если данных больше. Чтобы увидеть строку, введите её номер в поле Номер первой строки.
Подключение к кластеру
В основном вы будете работать с БД из приложений или из командной строки. Однако для этого нужно подключиться к хосту, на котором развёрнута БД.
Есть два варианта подключения. Если публичный доступ к хосту открыт, подключитесь к нему через интернет с помощью защищённого SSL-соединения. Если публичного доступа нет, подключитесь к хосту с виртуальной машины, созданной в той же виртуальной сети. SSL-соединение можно не использовать, но тогда трафик между виртуальной машиной и БД шифроваться не будет.
Давайте подключимся к БД через интернет и создадим в ней ещё одну таблицу. Для выполнения этого задания вы можете использовать виртуальную машину с Ubuntu.
Для создания таблицы сделаем в текстовом редакторе файл createTables.sql с командами. Например, такой:
CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT,
nickname VARCHAR(128) NOT NULL,
avatar VARCHAR(255),
mail VARCHAR(255),
PRIMARY KEY (user_id)
) ENGINE=INNODB;
Чтобы выполнить этот запрос в БД, подключимся к хосту. Для этого понадобится SSL-сертификат. Команды для его получения в Ubuntu:
mkdir ~/.mysql
wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.mysql/root.crt
chmod 0600 ~/.mysql/root.crt
Чтобы получить команды для подключения к БД, в консоли управления перейдите на страницу кластера, на вкладке Обзор нажмите кнопку Подключиться. В результате их выполнения в директории /home/<домашняя_директория>/.mysql/ сохранится SSL-сертификат root.crt.
Установите утилиту mysql-client, если на вашем компьютере или виртуальной машине её нет.
sudo apt update
sudo apt install -y mysql-client
Чтобы подключиться к БД, введите команду mysql. Для запуска нашего скрипта она выглядит следующим образом:
mysql --host=<адрес хоста> \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=<имя пользователя> \
--password \
<имя_базы_данных> < createTables.sql
Сервис помогает заполнить параметры в команде. Чтобы посмотреть пример команды с адресом хоста, именами пользователя и БД, в консоли управления перейдите на страницу кластера, на вкладке Обзор нажмите кнопку Подключиться.
После запуска команды введите пароль к БД, после чего в ней будет создана таблица users.
Если при создании кластера вы не включили публичный доступ, то к БД можно подключиться с виртуальной машины из той же облачной сети без использования шифрования. \\Следовательно, в этом случае в команде для подключения опускается параметр --ssl-ca, а --ssl-mode передаётся со значением DISABLED:
mysql --host=адрес_хоста \
--port=3306 \
--ssl-mode=DISABLED \
--user=<имя пользователя> \
--password \
<имя_базы_данных> < createTables.sql
Естественно, подключаться к БД можно не только из командной оболочки, но и из приложений. Нажмите уже знакомую вам кнопку Подключиться и посмотрите примеры кода для Python, PHP, Java, Node.js, Go, Ruby или настроек для драйвера ODBC.
Если вы хотите перенести БД в облако, то понадобится создать дамп и восстановить его в нужном кластере. Дамп — это копия БД или её части, представляющая собой текстовый файл с командами SQL (например, CREATE TABLE или INSERT). Его создают с помощью утилиты mysqldump.
Давайте попробуем перенести данные в кластер с помощью дампа. Для этого воспользуемся тестовой БД с данными о сотрудниках компании (имя, дата рождения, дата найма, место работы, зарплата и т. д.). Размер БД — около 167 Мб.
Скачайте из репозитория и сохраните на компьютере файлы с расширениями .sql и .dump. В файле employees.sql содержатся SQL команды, необходимые для создания таблиц и добавления в них данных из dump-файлов. Для переноса тестовой БД в облако понадобится запустить этот файл. Но, прежде чем приступить к переносу БД, откройте этот файл и удалите или закомментируйте (допишите в начало строки --) в нём строку 110. В этой строке расположена команда FLUSH LOGS, которая закрывает и снова открывает файлы журналов, а они в этой тестовой БД отсутствуют.
Создайте базу данных employees через консоль управления. Для этого на странице кластера перейдите на вкладку Базы данных и нажмите кнопку Добавить.
Добавьте пользователю, например user1, разрешение на доступ к БД employees. Для этого на странице кластера перейдите на вкладку Пользователи, напротив пользователя user1 нажмите кнопку ··· и выберите Настроить. Во всплывающем окне нажмите Добавить базу данных, выберите employees, добавьте роль ALL_PRIVILEGES и нажмите Сохранить.
Затем в командной строке перейдите в папку сохраненными файлами .sql и .dump и восстановите данные из дампа с помощью команды:
mysql --host=<адрес хоста> \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=<имя_пользователя> \
--password \
employees < ~/employees.sql
После того как данные скопируются, ваш кластер и БД будут готовы к работе. Подключитесь к БД в консоли управления и убедитесь, что данные перенесены.
Decision:
$ mkdir ~/.mysql
$ wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.mysql/root.crt
$ chmod 0600 ~/.mysql/root.crt
$ sudo apt update
$ sudo apt install mysql-client
$ vim createTables.sql
$ cat createTables.sql
CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT,
nickname VARCHAR(128) NOT NULL,
avatar VARCHAR(255),
mail VARCHAR(255),
PRIMARY KEY (user_id)
) ENGINE=INNODB;
$ mysql --host=rc1a-642tdtv6ope6gk7u.mdb.yandexcloud.net \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=tuser \
--password \
tdb < createTables.sql
$ wget https://github.com/datacharmer/test_db/archive/refs/heads/master.zip
$ unzip master.zip
$ ls test_db-master/
Changelog employees.sql load_dept_emp.dump load_salaries1.dump load_titles.dump sakila test_employees_md5.sql
employees_partitioned_5.1.sql images load_dept_manager.dump load_salaries2.dump objects.sql show_elapsed.sql test_employees_sha.sql
employees_partitioned.sql load_departments.dump load_employees.dump load_salaries3.dump README.md sql_test.sh test_versions.sh
$ cat test_db-master/employees.sql | grep flush
flush /*!50503 binary */ logs;
$ vim test_db-master/employees.sql
$ cat test_db-master/employees.sql | grep flush
$ cd test_db-master/
$ mysql --host=rc1a-642tdtv6ope6gk7u.mdb.yandexcloud.net \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=tuser1 \
--password \
employees < employees.sql
$ mysql --host=rc1a-642tdtv6ope6gk7u.mdb.yandexcloud.net \
--port=3306 \
--ssl-ca=~/.mysql/root.crt \
--ssl-mode=VERIFY_IDENTITY \
--user=tuser1 \
--password \
employees
mysql> show tables;
Task:
Создание кластера базы данных PostgreSQL.
В этой практической работе вы создадите кластер еще одной управляемой БД, на этот раз PostgreSQL, подключитесь к ней и загрузите в нее данные.
Decision:
Создание кластера управляемой базы данных PostgreSQL аналогично созданию кластера базы данных MySQL.
Перейдите в сервис управляемых баз данных PostgreSQL и нажмите кнопку Создать кластер.
В появившемся окне настроек задайте необходимые параметры.
- Имя кластера и его описание. Выберите уникальное в облаке имя кластера. Описание опционально, поэтому можно оставить это поле пустым.
- В поле Окружение выберите PRODUCTION.
- Выберите версию PostgreSQL и класс хоста.
- Выберите размер и тип сетевого хранилища.
- Задайте атрибуты базы данных.
- Выберите из списка сеть, в которой будут находиться хосты кластера (для подключения потребуются публичные хосты).
- В блоке Хосты добавьте ещё два хоста в других зонах доступности для обеспечения отказоустойчивости кластера. База автоматически реплицируется.
- В блоке Дополнительные настройки задайте время начала резервного копирования и включите доступ из консоли управления.
- Нажмите кнопку Создать кластер.
Как и в случае с MySQL, к хостам кластера Managed Service for PostgreSQL можно подключиться двумя способами.
Через интернет. Если вы настроили публичный доступ для нужного хоста, то подключиться к нему можно с помощью SSL-соединения.
С виртуальных машин Yandex Cloud. Они должны быть расположены в той же облачной сети. Если к хосту нет публичного доступа, для подключения с таких виртуальных машин SSL-соединение использовать необязательно. Обратите внимание, что если публичный доступ в вашем кластере настроен только для некоторых хостов, автоматическая смена мастера может привести к тому, что вы не сможете подключиться к мастеру из интернета.
Установите клиент для подключения к БД PostgreSQL. Команда установки в Ubuntu: sudo apt update && sudo apt install -y postgresql-client
Скачайте сертификат для подключения к БД PostgreSQL:
mkdir -p ~/.postgresql
wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.postgresql/root.crt
chmod 0600 ~/.postgresql/root.crt
Пример команды для подключения можно посмотреть в консоли управления, нажав на кнопку Подключиться на странице кластера. Подключение с SSL происходит при помощи следующей команды:
psql "host=<FQDN_хоста> \
port=6432 \
sslmode=verify-full \
dbname=<имя базы данных> \
user=<имя пользователя базы данных> \
target_session_attrs=read-write"
Загрузка данных в базу данных из CSV. Одним из способов добавления данных в базу является их загрузка из csv-файла.
Предположим, вы используете БД для организации работы транспортной службы интернет-магазина. Вам нужно добавить в базу таблицу, содержащую данные о расстояниях между складом и пунктами самовывоза, а также о стандартном времени доставки товаров со склада в эти пункты. Создадим csv-файл, например DTM.csv, который содержит такие данные (100 - код склада, 101-109 - коды пунктов, Time - стандартное время доставки в минутах, Distance - расстояние в километрах):
"depot","store","time","distance"
"100","101",31,12
"100","102",38,17
"100","103",56,33
"100","104",70,60
"100","105",41,25
"100","106",21,8
"100","107",33,14
"100","108",62,42
"100","109",45,29
Важные моменты при миграции из CSV:
- Названия колонок в файле и в таблице необязательно совпадают.
- Файл содержит заголовок, который не нужно импортировать.
- Первые 2 колонки конвертируем из строк (string) в целые числа (int).
PostgreSQL позволяет импортировать данные из файла несколькими способами:
- Командой copy.
- Через функции pl/pgsql.
- Средствами другого языка, например Python.
Воспользуемся первым способом. Сначала нам понадобится создать таблицу, в которую будет осуществлена миграция данных. Подключитесь к БД согласно инструкциям выше. Выполните следующую команду:
CREATE TABLE dtm (
id serial PRIMARY KEY,
depot int NOT NULL,
store int NOT NULL,
time int NOT NULL,
distance int NOT NULL
);
Загрузите данные: \copy dtm(depot,store,time,distance) from '/<путь к файлу>/DTM.csv' DELIMITERS ',' CSV HEADER;
В этой команде мы учли те моменты, о которых говорили вначале:
- dtm (depot, store, time, distance) маппинг колонок связывает колонки в файле с колонками в таблице, их имена могут не совпадать
- CSV HEADER показывает, что заголовок импортировать не нужно
- Колонки в таблице уже имеют правильные типы данных, конвертация будет выполнена автоматически.
В консоли управления на странице кластера перейдите на вкладку SQL. Введите пароль пользователя БД и нажмите кнопку Подключиться. Выберите таблицу dtm, чтобы убедиться, что добавление данных выполнено правильно.
Decision:
$ sudo apt update && sudo apt install postgresql-client
$ mkdir -p ~/.postgresql
$ wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O ~/.postgresql/root.crt
$ chmod 0600 ~/.postgresql/root.crt
$ vim DTM.csv
$ cat DTM.csv
"deport","store","time","distance"
"100","101",31,12
"100","102",38,17
"100","103",56,33
"100","104",70,60
"100","105",41,25
"100","106",21,8
"100","107",33,14
"100","108",62,42
"100","109",45,29
$ psql "host=rc1a-w3usdays081v0itf.mdb.yandexcloud.net,rc1c-qga7rd1sqe5jm8io.mdb.yandexcloud.net,rc1d-l1vzj1210qj68s8n.mdb.yandexcloud.net \
port=6432 \
sslmode=verify-full \
dbname=YOUR-DB \
user=YOUR-USERNAME \
target_session_attrs=read-write"
YOUR-DB=> CREATE TABLE dtm (
YOUR-DB(> id serial PRIMARY KEY,
YOUR-DB(> depot int NOT NULL,
YOUR-DB(> store int NOT NULL,
YOUR-DB(> time int NOT NULL,
YOUR-DB(> distance int NOT NULL
YOUR-DB(> );
YOUR-DB=> \copy dtm(depot,store,time,distance) from '/home/test/DTM.csv' DELIMITERS ',' CSV HEADER;
YOUR-DB=> exit
Task:
Создание кластера MongoDB.
На этом уроке вы создадите кластер MongoDB, подключитесь к нему и загрузите в него данные.
Раньше вы работали только с реляционными БД, но использование кластера MongoDB принципиально не отличается от работы с кластером MySQL или PostgreSQL, так что многое будет вам знакомо.
Decision:
Выберите в консоли управления Yandex Cloud каталог для кластера БД. На дашборде каталога откройте раздел Managed Service for MongoDB. В открывшемся окне нажмите кнопку Создать кластер.
Установите основные настройки кластера. Для этого урока создайте кластер с минимальной конфигурацией: тип хоста burstable, класс b2.nano, стандартное сетевое хранилище размером 10 ГБ. Откройте публичный доступ к хосту и задайте пароль пользователя БД. Остальные значения оставьте по умолчанию.
В сервисе управляемых БД MongoDB к хостам можно подключаться через интернет или с виртуальных машин в той же сети. Порт для подключения — 27018.
Для подключения через интернет хосты кластера должны находиться в публичном доступе. Подключаться можно только через зашифрованное соединение.
Обратите внимание: если публичный доступ настроен только для некоторых хостов в кластере, то при автоматической смене основной реплики она может оказаться недоступной из интернета.
Если к хосту нет публичного доступа и вы подключаетесь к нему с виртуальных машин Yandex Cloud, то зашифрованное соединение необязательно.
Подключитесь к созданной БД из интернета. Используйте SSL-сертификат, который вы подготовили на одной из предыдущих практических работ, или команду (для Ubuntu):
sudo mkdir -p /usr/local/share/ca-certificates/Yandex && \
sudo wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
Если всё пройдет успешно — вы получите сообщение операционной системы о том, что сертификат сохранён.
Установите утилиту Mongo Shell:
sudo apt install mongodb-clients
Подключитесь к БД с помощью команды mongo. Чтобы получить строку подключения, на основной странице сервиса в консоли управления выберите кластер, на вкладке Обзор нажмите кнопку Подключиться.
Сервис сформирует пример строки подключения для кластера. Там же вы можете посмотреть примеры кода на Python, PHP, Java, Node.js, Go для подключения из приложений.
Подключитесь к кластеру из командной строки.
mongo --norc \
--ssl \
--sslCAFile /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt \
--host '<FQDN хоста MongoDB>:27018' \
-u <имя пользователя БД> \
-p <пароль пользователя БД> \
<имя БД>
Создадим в БД коллекцию users. Предположим, в ней содержится информация о пользователях вашего приложения.
db.createCollection("users")
Загрузим в коллекцию тестовые данные с помощью методов добавления одного документа db.insertOne(...) и сразу нескольких db.insertMany(...).
Сначала добавим один документ (данные одного пользователя).
db.users.insertOne({firstName: "Adam", lastName: "Smith", age: 37, email: "adam.smith@test.com"});
Дополним коллекцию данными еще двух пользователей.
db.users.insertMany( [
{firstName: "Viktoria", lastName: "Holmes", age: 73, email: "viktoria.holmes@test.com", phone: "737772727"},
{firstName: "Tina", lastName: "Anders", age: 29, email: "tina.anders@test.com", children: [{firstName: "Sam", lastName: "Anders"},{firstName: "Anna", lastName: "Anders"}]}
] );
Обратите внимание, что документы в коллекции users содержат разный набор данных. С помощью MongoDB мы можем работать с данными, структура которых частично не совпадает.
Теперь посмотрим на содержимое коллекции с помощью команды db.users.find(). Результат показывает, что все данные успешно добавлены:
Проверим, есть ли среди пользователей те, кому больше 37 лет. Сделаем запрос к БД с помощью метода find.
db.users.find({age: {$gt: 37}});
Decision:
$ sudo mkdir -p /usr/local/share/ca-certificates/Yandex && \
sudo wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
$ wget https://downloads.mongodb.com/linux/mongodb-linux-x86_64-enterprise-ubuntu2004-6.0.2.tgz
$ tar -zxvf mongodb-linux-x86_64-enterprise-ubuntu2004-6.0.2.tgz
$ sudo ln -s /path/to/the/mongodb-directory/bin/* /usr/local/bin/
$ sudo apt install mongodb-clients
$ mongo --norc \
--ssl \
--sslCAFile /usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt \
--host 'rs01/rc1b-b7xwau9lvu3hdt0w.mdb.yandexcloud.net:27018' \
-u YOUR-USERNAME \
-p YOUR-PASSWORD \
YOUR-DB
rs01:PRIMARY> db.createCollection("users")
rs01:PRIMARY> db.users.insertOne({firstName: "Adam", lastName: "Smith", age: 37, email: "adam.smith@test.com"});
rs01:PRIMARY> db.users.insertMany( [
{firstName: "Viktoria", lastName: "Holmes", age: 73, email: "viktoria.holmes@test.com", phone: "737772727"},
{firstName: "Tina", lastName: "Anders", age: 29, email: "tina.anders@test.com", children: [{firstName: "Sam", lastName: "Anders"},{firstName: "Anna", lastName: "Anders"}]}
] );
rs01:PRIMARY> db.users.find({age: {$gt: 37}});
Task:
Создание кластера ClickHouse и подключение к нему.
В этой практической работе вы создадите кластер ClickHouse. Вы уже знаете, как создавать кластеры и выставлять их основные настройки в сервисах платформы данных. Но у БД ClickHouse есть свои особенности.
Когда вы создадите кластер из двух или более хостов, сервис дополнительно создаст ещё один кластер из трёх хостов, где развернёт Apache ZooKeeper. Это служба для распределенных систем, которая управляет конфигурацией, репликацией и распределением запросов по хостам БД. Без неё кластер ClickHouse работать не будет. К ZooKeeper у пользователей доступа нет, однако его хосты учитываются при расчёте квоты ресурсов облака и стоимости сервиса.
ZooKeeper синхронизирует шарды (т. е. хосты) ClickHouse. В отличие от классических реляционных БД, у ClickHouse нет главного узла (мастера), через который добавляются данные. В ClickHouse данные можно и записывать, и читать с любого узла.
Decision:
Перейдите в каталог, где нужно создать кластер БД, выберите Managed Service for ClickHouse и нажмите кнопку Создать кластер.
Для практической работы нам понадобится кластер с минимальной конфигурацией: тип хоста burstable, класс b2.nano и стандартное сетевое хранилище размером 10 ГБ.
Задайте настройки: введите имена для кластера и БД, а также имя и пароль пользователя. Откройте публичный доступ к хосту.
Обратите внимание: в отличие от сервисов, которые мы уже рассматривали, здесь в разделе База данных можно включить опции управления пользователями и БД с помощью SQL-запросов.
Кроме того, в дополнительных настройках можно включить доступ к БД из консоли управления, сервисов DataLens, Яндекс Метрики и AppMetrica, а также возможность использовать бессерверные вычисления (подробно о них мы расскажем на курсе «Serverless»). С помощью DataLens, например, вы визуализируете результаты поисковых запросов в виде графиков, диаграмм и дашбордов, а подключение AppMetrica позволит импортировать данные из этого сервиса в кластер.
Отметьте пункт Доступ из DataLens: он понадобится вам на одном из следующих уроков. Нажмите кнопку Создать кластер.
К хостам кластера ClickHouse можно подключаться через интернет или с виртуальных машин в той же виртуальной сети. Если к хостам БД открыт публичный доступ, то для подключения к ним используется шифрованное соединение.
Подключайтесь к кластеру с помощью HTTP-протокола или более низкоуровневого Native TCP-протокола. В большинстве случаев рекомендуется взаимодействовать с ClickHouse не напрямую, а с помощью инструмента или библиотеки. Официально поддерживаются консольный клиент, драйверы JDBC и ODBC, клиентская библиотека для C++. Также можно использовать библиотеки сторонних разработчиков для Python, PHP, Go, Ruby и т. д.
Примеры строк подключения приводятся в документации и консоли управления на вкладке Обзор страницы кластера.
С БД удобно работать в приложении с графическим интерфейсом. Один из вариантов — универсальный клиент DBeaver. Другие варианты вы найдёте в полном списке клиентов.
Подробная информация о настройке подключения приведена в документации. Чтобы создать подключение к ClickHouse в DBeaver, помимо обычных параметров (адреса хоста, порта, имени БД, логина и пароля) задайте на вкладке Свойства драйвера настройки свойств драйвера JDBC. Укажите следующие параметры: ssl = true; sslmode = strict; sslrootcert = <путь к SSL-сертификату>. Как получить SSL-сертификат, вы уже узнали на предыдущих уроках.
При подключении DBeaver покажет номер версии ClickHouse и пинг до хоста.
В двух следующих практических работах мы используем кластер для аналитической работы с датасетами и для создания БД ClickHouse.
Decision:
$ wget https://dbeaver.io/files/dbeaver-ce_latest_amd64.deb
$ sudo dpkg -i dbeaver-ce_latest_amd64.deb
$ dbeaver-ce &
$ mkdir -p ~/.clickhouse-client
$ sudo wget "https://storage.yandexcloud.net/mdb/clickhouse-client.conf.example" -O ~/.clickhouse-client/config.xml
$ sudo apt-get install -y apt-transport-https ca-certificates dirmngr
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754
$ echo "deb https://packages.clickhouse.com/deb stable main" | sudo tee \
/etc/apt/sources.list.d/clickhouse.list
$ sudo apt-get update
$ sudo apt-get install -y clickhouse-server clickhouse-client
$ clickhouse-client --host rc1a-mg8yquor7pspcwkc.mdb.yandexcloud.net \
--secure \
--user YOUR-USERNAME \
--database YOUR-DB \
--port 9440 \
--ask-password
Task:
Работа с данными из объектного хранилища.
В интернете выложено множество датасетов — структурированных наборов данных, связанных общей темой. Например в репозитории проекта Our World in Data находится около тысячи разнообразных датасетов: от численности населения государств до сведений об употреблении алкоголя в США с 1850 года.
Датасеты часто выкладывают в виде CSV- или TSV-файлов. В них значения разделены запятой (comma separated values, CSV) или табуляцией (tab separated values, TSV).
Сохраняйте датасеты в объектное хранилище и анализируйте данные с помощью ClickHouse. При этом не требуется создавать БД и копировать в неё данные из датасета. Отправляйте запросы к ClickHouse — а ClickHouse сходит за данными напрямую в объектное хранилище.
Decision:
В качестве примера возьмем датасет с историей метеонаблюдений за 10 лет и попробуем развеять мифы о разнице погоды в Москве и Санкт-Петербурге. Датасет содержит примерно 50 тысяч записей, он выложен в объектном хранилище Yandex Cloud и доступен всем.
Воспользуемся кластером БД, который мы создали на предыдущем уроке. Откройте его в консоли управления. Запросы к датасету будем делать через SQL-консоль. На панели слева выберите вкладку SQL и введите пароль пользователя. В правом поле открывшейся консоли мы и станем вводить SQL-запросы.
Как вы думаете, где зарегистрирована самая низкая температура? Наверняка в Санкт-Петербурге! Давайте проверим.
Выполните запрос:
SELECT
City,
LocalDate,
TempC
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
ORDER BY TempC ASC
LIMIT 1
Всё-таки наши интуитивные представления не всегда верны и могут опровергаться данными.
А что насчет самой высокой температуры, скорости ветра и влажности? Проверьте сами, изменив поля в запросе (средняя скорость ветра за 10 минут — WindSpeed10MinAvg, относительная влажность — RelHumidity; сортировка по возрастанию — ASC, по убыванию — DESC). Увеличив количество выводимых данных, вы получите более точное представление (измените параметр LIMIT c 1 до 10).
Но это были крайние значения. Давайте проверим, насколько в этих городах отличается климат в целом. Узнаем, например, разницу среднегодовых температур.
SELECT
Year,
msk.t - spb.t
FROM
(
SELECT
toYear(LocalDate) AS Year,
avg(TempC) AS t
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
WHERE City = 'Moscow'
GROUP BY Year
ORDER BY Year ASC
) AS msk
INNER JOIN
(
SELECT
toYear(LocalDate) AS Year,
avg(TempC) AS t
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
WHERE City = 'Saint-Petersburg'
GROUP BY Year
ORDER BY Year ASC
) AS spb ON msk.Year = spb.Year
Измените поля в запросе, чтобы проверить разницу относительной влажности.
Давайте теперь рассчитаем, где раньше начинается лето. Будем считать началом лета день, начиная с которого температура поднималась выше +15 °С хотя бы пять раз в течение 10-дневного периода (864 тысячи секунд).
SELECT
City,
toYear(LocalDate) AS year,
MIN(LocalDate)
FROM
(
SELECT
City,
LocalDate,
windowFunnel(864000)(LocalDateTime, TempC >= 15, TempC >= 15, TempC >= 15, TempC >= 15, TempC >= 15) AS warmdays
FROM s3(
'https://storage.yandexcloud.net/arhipov/weather_data.tsv',
'TSV',
'LocalDateTime DateTime, LocalDate Date, Month Int8, Day Int8, TempC Float32,Pressure Float32, RelHumidity Int32, WindSpeed10MinAvg Int32, VisibilityKm Float32, City String')
GROUP BY
City,
LocalDate
)
WHERE warmdays = 5
GROUP BY
year,
City
ORDER BY
year ASC,
City ASC
Task:
Добавление данных.
Предположим, вы работаете в метеорологической службе и постоянно изучаете датасеты с погодными данными.
Сбор данных о погоде автоматизирован: на территории области расположены несколько десятков пунктов наблюдения с датчиками.
Информация о температуре, давлении, влажности и скорости ветра раз в полчаса передаётся с датчиков на центральный сервер.
Приложение на сервере обрабатывает данные, переводит их в нужный формат и записывает в файл. Каждый файл содержит данные за три часа наблюдений.
Для прогноза нужно учитывать всю историю наблюдений за последние несколько лет, то есть все файлы потребуется собрать в одну БД.
Давайте потренируемся добавлять данные из файлов в БД ClickHouse.
На предыдущих уроках мы создали кластер, на котором развёрнута БД, и научились подключаться к нему.
Продолжим работать с этой БД, а в качестве добавляемого файла возьмем уже известный вам датасет с данными о погоде в Москве и Санкт-Петербурге.
Decision:
Сохраните файл на компьютере.
Прежде чем добавлять файл в БД, создадим в ней таблицу, куда будут вставляться данные. Перейдите в SQL-консоль кластера и выполните команду:
CREATE TABLE <имя вашей БД>.Weather
( LocalDateTime DateTime,
LocalDate Date,
Month Int8,
Day Int8,
TempC Float32,
Pressure Float32,
RelHumidity Int32,
WindSpeed10MinAvg Int32,
VisibilityKm Float32,
City String
) ENGINE=MergeTree
ORDER BY LocalDateTime;
В результате будет создана пустая таблица с полями и типами данных, соответствующими полям и типам данных в нашем файле (датасете).
Вставим данные в таблицу с помощью клиента командной строки clickhouse-client. Команды для его установки (для Ubuntu):
sudo apt update && sudo apt install --yes apt-transport-https ca-certificates dirmngr && \
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E0C56BD4 && \
echo "deb https://repo.clickhouse.com/deb/stable/ main/" | sudo tee \
/etc/apt/sources.list.d/clickhouse.list
sudo apt update && sudo apt install --yes clickhouse-client
mkdir --parents ~/.clickhouse-client && \
wget "https://storage.yandexcloud.net/mdb/clickhouse-client.conf.example" \
--output-document ~/.clickhouse-client/config.xml
Подробности о том, как установить клиент и работать с ним, вы найдёте в документации ClickHouse.
Подключитесь к кластеру. Пример строки подключения посмотрите в консоли управления.
Добавим файл с данными в БД с помощью команды:
cat weather_data.tsv | clickhouse-client \
--host <адрес вашей БД> \
--secure \
--user user1 \
--database db1 \
--port 9440 \
-q "INSERT INTO db1.Weather FORMAT TabSeparated" \
--ask-password
Переключившись в SQL-консоль, вы увидите, что данные появились в таблице.
Данные в БД можно загружать и другими способами: из приложений или клиентов с графическим интерфейсом (например DBeaver). В этом случае подключение к БД и передача данных будут идти по HTTP-протоколу через порт 8443.
Теперь вы можете анализировать 10-летний срез данных о погоде в Москве и Санкт-Петербурге непосредственно в ClickHouse, без обращений к внешним источникам. Попробуйте, например, выяснить, какой день был самым ветреным в этих городах.
После практической работы остановите кластер, но не удаляйте его. Кластер ещё понадобится, когда мы будем рассматривать сервис визуализации и анализа данных Yandex DataLens.
Decision:
$ wget https://storage.yandexcloud.net/arhipov/weather_data.tsv
$ cat weather_data.tsv | clickhouse-client \
--host YOUR-IP \
--secure \
--user YOUR-USERNAME \
--database YOUR-DB \
--port 9440 \
-q "INSERT INTO YOUR-DB.Weather FORMAT TabSeparated" \
--ask-password
Task:
Создание базы данных.
В этой практической работе вы создадите БД YDB в dedicated режиме, научитесь подключаться к ней и добавлять данные из тестового приложения.
Также вы подключитесь к БД и запустите тестовое приложение, чтобы создать в ней несколько таблиц с данными о популярных сериалах.
Decision:
На стартовой странице консоли управления перейдите в каталог, в котором будете создавать БД, выберите в списке сервисов База данных YDB и нажмите кнопку Создать ресурс.
В открывшемся окне выберите тип БД dedicated. Появившийся интерфейс создания новой БД практически идентичен уже знакомым вам интерфейсам создания кластеров управляемых БД.
Выберите для вашей БД имя, назначьте необходимые вычислительные ресурсы (для этой и следующих практических работ достаточно одного хоста конфигурации medium), тип и количество групп хранения (достаточно одной группы).
Группа хранения – это массив независимых дисковых накопителей, объединённых по сети в единый логический элемент. В YDB такой массив состоит из 9 дисков, расположенных по три в каждой из трёх зон доступности. Такая конфигурация обеспечивает устойчивость при одновременном отказе одной из зон и отказе диска в другой зоне. Стандартный размер группы хранения — 100 ГБ.
Выберите облачную сеть и подсети для работы с БД. Вы можете оставить сеть по умолчанию или выбрать ту, которую создали на предыдущем курсе. БД будет доступна для всех виртуальных машин, которые подключены к той же облачной сети.
Также выберите опцию присвоения публичного IP-адреса, чтобы иметь возможность подключаться к БД из интернета.Нажмите кнопку Создать базу данных. Создание БД занимает несколько минут. Когда статус БД изменится с Provisioning на Running, она готова к работе. Кликнув на созданную БД в консоли управления, вы перейдёте на вкладку Обзор.
В разделе Соединение на этой странице приведена информация, которая вам понадобится для подключения к БД:
- Эндпоинт — точка подключения с указанием протокола, представляющая собой в данном случае адрес, на который посылаются сообщения;
- Размещение базы данных — полный путь к БД.
- Примеры подключений из командной строки и приложений вы можете посмотреть, нажав на кнопку Подключиться.
Подключение к базе данных и запуск тестового приложения.
Для того, чтобы выполнить эту задачу, вам понадобится сервисный аккаунт с ролями viewer и editor. Перейдите в дашборд каталога и выберите вкладку Сервисные аккаунты. Создайте сервисный аккаунт, назначив для него указанные роли. Сохраните идентификатор этого аккаунта.
Вы можете запускать тестовое приложение со своего компьютера или с виртуальной машины в Yandex Cloud. В данном примере используется OC Ubuntu и приложение на Python.
Если при создании БД вы не присвоили ей публичный IP-адрес, то подключиться к ней вы сможете только с виртуальной машины, расположенной в той же облачной сети.
Для запуска приложения нужно склонировать на свою машину репозиторий YDB Python SDK, из которого оно будет вызываться, а также установить библиотеки ydb, iso8601 и yandexcloud. Воспользуйтесь для этого следующими командами:
git clone https://github.com/yandex-cloud/ydb-python-sdk.git
sudo pip3 install iso8601 ydb yandexcloud
Создайте авторизованный ключ для вашего сервисного аккаунта и сохраните его в файл с помощью интерфейса командной строки Yandex Cloud.
mkdir ~/.ydb
yc iam key create \
--folder-id <идентификатор каталога> \
--service-account-name <имя сервисного аккаунта> \
--output ~/.ydb/sa_name.json
Получите SSL-сертификат:
wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" \
-O ~/.ydb/CA.pem
Установите переменную окружения YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS и переменную окружения с SSL-сертификатом.
export YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS=~/.ydb/sa_name.json
export YDB_SSL_ROOT_CERTIFICATES_FILE=~/.ydb/CA.pem
Запустите тестовое приложение basic_example_v1 из репозитория ydb-python-sdk, указав в качестве параметров подключения значения протокола, эндпоинта и полного пути к БД.
cd ./ydb-python-sdk/examples/basic_example_v1
python3 __main__.py \
-e <Эндпоинт> \
-d <Размещение базы данных>
Результат выполнения приложения должен выглядеть так:
> describe table: series
column, name: series_id , Uint64
column, name: title , Utf8
column, name: series_info , Utf8
column, name: release_date , Uint64
> select_simple_transaction:
series, id: 1 , title: IT Crowd , release date: b'2006-02-03'
> bulk upsert: episodes
> select_prepared_transaction:
episode title: To Build a Better Beta , air date: b'2016-06-05'
> select_prepared_transaction:
episode title: Bachman's Earnings Over-Ride , air date: b'2016-06-12'
> explicit TCL call
> select_prepared_transaction:
episode title: TBD , air date: b'2022-08-24'
Вернитесь в консоль управления Yandex Cloud, чтобы посмотреть на результаты работы приложения. Переключитесь на вкладку Навигация.
В вашей БД созданы три таблицы: episodes, seasons и series с информацией о двух популярных сериалах IT Crowd и Silicon Valley. Кликнув по названию таблицы, вы увидите содержащиеся в ней данные. А если подвести к названию таблицы курсор и кликнуть на значок «информация» справа, то внизу появится дополнительное окно с вкладками Обзор, Схема и Партиции.
Кнопка Создать на панели Навигация служит для создания директорий и таблиц. С её помощью можно создать новую таблицу, не прибегая к командам YQL.
Decision:
$ git clone https://github.com/yandex-cloud/ydb-python-sdk.git
$ mkdir ~/.ydb
$ yc iam key create \
--service-account-name sa-ydb \
--output ~/.ydb/authorized_key.json
$ export YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS=~/.ydb/authorized_key.json
$ cd ydb-python-sdk/examples/basic_example_v1
$ python3 __main__.py \
-e <Эндпоинт> \
-d <Размещение базы данных>
Task:
YQL и работа с данными
В этом уроке вы освоите базовый набор операций для работы с данными с использованием YQL и консоли управления Yandex.Cloud.
Decision:
Чтобы начать, войдите в раздел Навигация консоли управления и откройте редактор SQL, нажав на кнопку SQL-запрос.
На прошлом уроке мы уже создали в нашей БД три таблицы, содержащие информацию о сериалах IT Crowd и Silicon Valley.
Добавим в БД еще одну таблицу с рейтингами эпизодов сериала IT Crowd на IMDb.com.
YQL является диалектом SQL, поэтому многие инструкции в этих языках идентичны.
Для создания таблицы вам понадобится сделать запрос к БД, содержащий инструкцию CREATE TABLE. Например, если бы мы хотели создать таблицу seasons (она уже есть в вашей БД), то SQL запрос выглядел бы следующим образом:
CREATE TABLE seasons
(
series_id Uint64,
season_id Uint64,
first_aired Date,
last_aired Date,
title Utf8,
PRIMARY KEY (series_id, season_id)
);
Обратите внимание, что в пределах директории YDB имена таблиц должны быть уникальны. Первичный ключ (PRIMARY KEY) — это столбец или комбинация столбцов, однозначно идентифицирующих каждую строку в таблице. Он может содержать только неповторяющиеся значения. Для таблицы YDB указание первичного ключа обязательно, при этом он может быть только один.
Первичный ключ по сути является первичным индексом, который помогает СУБД быстрее обнаруживать отдельные записи в таблице и сокращает время выполнения запросов. Также в таблицу можно добавить один или несколько вторичных индексов. Они служат той же цели, но в отличие от первичного индекса могут содержать повторяющиеся значения. Добавить вторичные индексы можно в любой момент, когда возникнет необходимость, и это не вызовет деградацию производительности БД. Чтобы при создании таблицы добавить в нее вторичный индекс, используется такая конструкция:
INDEX <имя индекса> GLOBAL ON (<имя столбца1>, <имя столбца2>, ...)
Вторичный индекс можно добавить и в уже существующую таблицу. Работа БД при этом не прерывается. В отличие от предыдущего случая в существующую таблицу можно добавлять только один вторичный индекс за раз. Делается это с помощью следующей команды:
ALTER TABLE <имя таблицы> ADD INDEX <имя индекса> GLOBAL ON (<имя столбца>);
Task:
создайте таблицу ratings, в которой будут содержаться рейтинги всех эпизодов сериала IT Crowd, со столбцами season_id (Uint64), episodes_id (Uint64), title (Utf8), air_date (Date) и imdb_rating (Uint64) и вторичным индексом rating_index по полю imdb_rating.
Decision:
CREATE TABLE ratings (
season_id Uint64,
episodes_id Uint64,
title Utf8,
air_date Date,
imdb_rating Uint64,
PRIMARY KEY (season_id, episodes_id),
INDEX rating_index GLOBAL ON (imdb_rating)
);
Decision:
Добавим в эту таблицу данные. Для вставки данных в YDB помимо обычной SQL инструкции INSERT также используются инструкции REPLACE и UPSERT.
При выполнении INSERT перед операцией записи выполняется операция чтения данных. Это позволяет убедиться, что уникальность первичного ключа будет соблюдена. При выполнении инструкций REPLACE и UPSERT осуществляется слепая запись.
Инструкции REPLACE и UPSERT используются для добавления новой или изменения существующей строки по заданному значению первичного ключа. При операциях записи и изменения данных использование этих инструкций эффективнее.
Если при выполнении этих инструкций строка с указанным значением первичного ключа не существует, то она будет создана. Если же такая строка существует, то значения ее столбцов будут заменены на новые. Отличие между REPLACE и UPSERT заключается в том, что первая из этих инструкций устанавливает значения столбцов, не участвующих в операции, в значения по умолчанию, а вторая такие значения не меняет.
Одним запросом REPLACE, UPSERT или INSERT можно вставить в таблицу несколько строк.
Например, если бы мы хотели добавить в таблицу series те данные, которые в ней сейчас содержатся, то SQL запрос выглядел бы так:
REPLACE INTO series (series_id, title, release_date, series_info)
VALUES
(
1,
"IT Crowd",
Date("2006-02-03"),
"The IT Crowd is a British sitcom produced by Channel 4, written by Graham Linehan, produced by Ash Atalla and starring Chris O'Dowd, Richard Ayoade, Katherine Parkinson, and Matt Berry."),
(
2,
"Silicon Valley",
Date("2014-04-06"),
"Silicon Valley is an American comedy television series created by Mike Judge, John Altschuler and Dave Krinsky. The series focuses on five young men who founded a startup company in Silicon Valley."
);
Task:
добавьте в таблицу ratings данные из этого файла.
Decision:
REPLACE INTO ratings (season_id, episodes_id, title, air_date, imdb_rating) VALUES
(1, 1, "Yesterday's Jam", Date("2006-02-03"), 76),
(1, 2, "Calamity Jen", Date("2006-02-03"), 82),
(1, 3, "Fifty-Fifty", Date("2006-02-10"), 79),
(1, 4, "The Red Door", Date("2006-02-17"), 80),
(1, 5, "The Haunting of Bill Crouse", Date("2006-02-24"), 85),
(1, 6, "Aunt Irma Visits", Date("2006-03-03"), 81),
(2, 1, "The Work Outing", Date("2006-08-24"), 95),
(2, 2, "Return of the Golden Child", Date("2007-08-31"), 82),
(2, 3, "Moss and the German", Date("2007-09-07"), 82),
(2, 4, "The Dinner Party", Date("2007-09-14"), 87),
(2, 5, "Smoke and Mirrors", Date("2007-09-21"), 78),
(2, 6, "Men Without Women", Date("2007-09-28"), 76),
(3, 1, "From Hell", Date("2008-11-21"), 78),
(3, 2, "Are We Not Men?", Date("2008-11-28"), 85),
(3, 3, "Tramps Like Us", Date("2008-12-05"), 82),
(3, 4, "The Speech", Date("2008-12-12"), 90),
(3, 5, "Friendface", Date("2008-12-19"), 85),
(3, 6, "Calendar Geeks", Date("2008-12-26"), 78),
(4, 1, "Jen The Fredo", Date("2010-06-25"), 80),
(4, 2, "The Final Countdown", Date("2010-07-02"), 84),
(4, 3, "Something Happened", Date("2010-07-09"), 75),
(4, 4, "Italian For Beginners", Date("2010-07-16"), 82),
(4, 5, "Bad Boys", Date("2010-07-23"), 84),
(4, 6, "Reynholm vs Reynholm", Date("2010-07-30"), 76);
Decision:
C помощью SQL запросов можно добавлять и удалять не только строки таблицы, но и столбцы. Для этого используется команда ALTER TABLE и фразы ADD COLUMN и DROP COLUMN.
Например, если вы хотите добавить в таблицу ratings столбец viewed с данными о том, какие эпизоды сериала вы уже посмотрели, то это можно сделать с помощью следующей команды.
ALTER TABLE ratings ADD COLUMN viewed Bool;
Task:
Вы решили, что столбец с датой выхода эпизодов в таблице ratings не нужен, поскольку эта информация уже содержится в другой таблице. Удалите столбец air_date из таблицы ratings.
Decision:
ALTER TABLE ratings DROP COLUMN air_date;
Decision:
Теперь потренируемся извлекать данные из БД. Для этого используется команда SELECT. В простейшем случае ее синтаксис выглядит так:
SELECT <имя столбца1>, <имя столбца2>, ...
FROM <имя таблицы>;
Например, чтобы выбрать всю информацию из таблицы seasons, нужно сделать следующий запрос к БД.
SELECT * FROM seasons;
Если нужно выбрать из таблицы только те строки, которые удовлетворяют определенному условию, в запросе используют секцию WHERE. В этой секции должно находиться выражение, возвращающее логический результат. Обычно оно состоит из логических операций and, or, not и операций сравнения.
Например, выбрать из таблицы episodes только первые эпизоды всех сезонов можно так:
SELECT * FROM episodes
WHERE episode_id = 1;
Запрос SELECT извлекает строки без определенного порядка. Чтобы отсортировать полученные данные нужным образом, в этот запрос включают секцию ORDER BY. В ней указывается список столбцов, которые будут определять порядок сортировки результатов запроса.
Task:
получите список самых популярных (с рейтингом не менее 85) эпизодов сериала IT Crowd. При поиске используйте созданный ранее вторичный индекс rating_index. Чтобы упорядочить результаты по убыванию рейтинга используйте конструкцию ORDER BY … DESC.
Decision:
SELECT
season_id,
episodes_id,
title,
imdb_rating
FROM ratings VIEW rating_index
WHERE
imdb_rating >= 85
ORDER BY
imdb_rating DESC;
Decision:
Для получения обобщённых сведений о содержащихся в таблице данных — например, о числе строк в таблице или среднем значении какого-либо выражения — в запрос SELECT включают агрегатные функции и секцию GROUP BY. Эта секция используется для агрегации внутри каждого ключа. Ключом является значение одной или более колонок, указанных в GROUP BY.
Примеры агрегатных функций:
COUNT(*) — вычисляет число строк в таблице.
MAX(expr) — находит максимум выражения expr по всем строкам.
SUM(expr) — суммирует выражение expr по всем строкам. Тип выражения должен быть числовым.
AVG(expr) — находит среднее значение выражения expr по всем строкам. Тип выражения должен быть числовым или интервалом.
SOME(expr) — возвращает одно произвольное значение выражения по всем строкам.
Результаты выполнения агрегатной функции выводятся в отдельном столбце. Чтобы задать этому столбцу имя, используют оператор AS. Конструкция может выглядеть, например, так:
SELECT
<имя столбца1>,
MAX(<имя столбца2>) AS max_value
...;
Task:
Напишите SQL запрос к таблице episodes, который выводит данные о числе эпизодов каждого сериала.
Вам понадобится вычислить число строк для каждого значения столбца series_id и сгруппировать результаты по series_id.
Decision:
SELECT
series_id,
COUNT(*) AS total_episodes
FROM episodes
GROUP BY
series_id
ORDER BY
series_id;
Task:
Напишите SQL запрос, с помощью которого можно сравнить популярность сезонов сериала IT Crowd.
Вам понадобится вычислить средний рейтинг эпизодов для каждого сезона и сгруппировать результаты по столбцу season_id.
Decision:
SELECT
season_id,
AVG (imdb_rating) AS avg_rating
FROM ratings
GROUP BY season_id
ORDER BY avg_rating DESC;
Decision:
В реляционной БД таблицы логически связаны друг с другом. С помощью объединений (JOIN) можно получить данные из нескольких связанных друг с другом таблиц и представить их в виде одной результирующей таблицы.
Столбцы, по которым выполняется объединение, можно указать одним из двух способов.
- После ключевого слова USING, например table1 AS a JOIN table2 AS b USING (foo). Это более короткий способ записи, удобный для простых случаев. Имена столбцов, по которым происходит объединение таблиц, должны быть одинаковы.
- После ключевого слова ON (например, a JOIN b ON a.foo = b.bar). Этот способ позволяет использовать разные имена столбцов и указывать дополнительные условия по аналогии с WHERE.
Поскольку такие запросы затрагивают столбцы разных таблиц, имена столбцов должны содержать и имя таблицы (то есть, например, не просто series_id, а seasons.series_id).
В YDB доступны следующие логические типы объединений:
INNER (используется по умолчанию) — строки попадают в результат, только если значение ключевых колонок присутствует в обеих таблицах;
FULL, LEFT и RIGHT — при отсутствии значения в обеих или в одной из таблиц включает строку в результат, но оставляет пустыми (NULL) колонки, соответствующие противоположной таблице.
LEFT/RIGHT SEMI — одна сторона выступает как белый список (whitelist) ключей, её значения недоступны. В результат включаются столбцы только из одной таблицы, декартового произведения не возникает;
LEFT/RIGHT ONLY — вычитание множеств по ключам (blacklist). Практически эквивалентно добавлению условия IS NULL на ключ противоположной стороны в обычном LEFT/RIGHT, но, как и в SEMI, нет доступа к значениям;
CROSS — декартово произведение двух таблиц целиком без указания ключевых колонок, секция с ON/USING явно не пишется;
EXCLUSION — обе стороны минус пересечение.
Простой пример запроса с объединением таблиц приведен ниже.
SELECT
sa.title AS season_title,
sr.title AS series_title,
sr.series_id, sa.season_id
FROM seasons AS sa
INNER JOIN series AS sr ON sa.series_id = sr.series_id
WHERE sa.season_id = 1
ORDER BY sr.series_id;
Этот запрос извлекает из таблиц series и seasons сведения о первых сезонах всех сериалов и выводит объединённые данные в результирующей таблице.
Task:
напишите запрос, который выводит таблицу, содержащую название сериала IT Crowd и названия всех его эпизодов (то есть, каждая строка итоговой таблице должна содержать название сериала и название отдельного эпизода).
Decision:
SELECT
sr.title AS series_title,
ep.title AS episode_title,
ep.season_id,
ep.episode_id
FROM
series AS sr
INNER JOIN
episodes AS ep
ON sr.series_id = ep.series_id
WHERE sr.series_id = 1
ORDER BY
ep.season_id,
ep.episode_id;
Task:
Создание кластера Hadoop.
На этом уроке вы создадите и настроите кластер Hadoop с помощью сервиса Yandex Data Proc.
Hadoop предназначается для работы с большими данными, поэтому создание кластера потребует от вас больше усилий, чем на предыдущих практических работах (но гораздо меньше, чем если бы вы делали это самостоятельно).
Decision:
Для хранения зависимостей заданий нашего кластера и результатов их выполнения нужно предварительно создать бакет в объектном хранилище. О том, как это сделать, мы рассказывали на одном из предыдущих занятий.
Также создайте сервисный аккаунт для доступа к кластеру. Обратите внимание: можно использовать только аккаунт с ролью mdb.dataproc.agent. Для автоматического масштабирования кластера сервисному аккаунту также понадобятся роли editor и dataproc.agent.
Откройте каталог, где будете создавать кластер, и выберите сервис Data Proc.
В открывшемся окне нажмите кнопку Создать кластер.
Задайте для кластера имя и выберите версию образа — 1.4. В образ включена одна из версий Hadoop и дополнительные компоненты. Некоторые вы можете устанавливать по выбору. Кроме того, в каждую версию образа входит Conda (менеджер окружений для Python) и набор инструментов машинного обучения (scikit-learn, TensorFlow, CatBoost, LightGBM и XGBoost).
Обратите внимание на то, что некоторые из сервисов обязательны, чтобы использовать другие. На следующем уроке нам понадобится сервис HIVE. Выберите его, и рядом с MAPREDUCE и YARN вы увидите напоминания о том, что они нужны для HIVE.
Вставьте в поле публичный ключ публичную часть SSH-ключа. Как сгенерировать и использовать SSH-ключи, мы рассказывали в одной из практических работ о виртуальных машинах.
Выберите созданный сервисный аккаунт для доступа к кластеру.
Выберите зону доступности для кластера. Все подкластеры будут находиться в этой
Если нужно, задайте свойства Hadoop и его компонентов. Доступные свойства перечислены в документации.
Выберите бакет в объектном хранилище, где будут храниться зависимости заданий и результаты их выполнения.
Выберите или создайте сеть для кластера. Включите опцию NAT в интернет для подсетей, в которых размещается кластер.
Если нужно, создайте группу безопасности. Правила для неё вы добавите позже в сервисе Virtual Private Cloud.
Включите опцию UI Proxy, чтобы получить доступ к веб-интерфейсам компонентов Data Proc. У некоторых компонентов (например Hadoop, Spark, YARN и Zeppelin) есть пользовательские веб-интерфейсы, доступные на мастер-узле кластера. С помощью этих интерфейсов вы можете:
- отслеживать ресурсы кластера и управлять ими (YARN Resource Manager, HDFS NameNode);
- просматривать статус и отлаживать задания (Spark History, JobHistory);
- проводить эксперименты, совместно работать или выполнять отдельные операции (Zeppelin).
Настройка подкластеров. В состав кластера входит один главный подкластер (Мастер) с управляющим хостом, а также подкластеры для хранения данных (Data) или вычислений (Compute).
В подкластерах Data можно разворачивать компоненты для хранения данных, а в подкластерах Compute — для обработки данных. Хранилище в подкластере Compute предназначено только для временного хранения обрабатываемых файлов.
Для каждого подкластера можно задать число и класс хостов, размер и тип хранилища, а также подсеть той сети, в которой расположен кластер. Кроме того, для подкластеров Compute можно настроить автоматическое масштабирование. Это позволит выполнять задания на обработку данных быстрее без дополнительных усилий с вашей стороны.
Создадим подкластер Compute с одним хостом.
В блоке Добавить подкластер нажмите кнопку Добавить.
В поле Роли выберите COMPUTENODE. В блоке Масштабирование включите опцию Автоматическое масштабирование.
Все открывшиеся настройки знакомы вам из практических работ по созданию виртуальных машин.
Автоматическое масштабирование подкластеров обработки данных поддерживается в кластерах Yandex Data Proc версии 1.2 и выше. Чтобы оно работало, в кластере с установленным Spark или Hive должен быть также установлен сервис YARN.
Yandex Data Proc автоматически масштабирует кластер, используя для этого системные метрики нагрузки на кластер. Когда их значение выходит из установленного диапазона, запускается масштабирование. Если значение метрики превысит порог, в подкластер добавятся хосты. Если опустится ниже порога, начнётся декомиссия (высвобождение ненужных ресурсов), а избыточные хосты удалятся.
По умолчанию для масштабирования используется внутренняя метрика YARN (yarn.cluster.containersPending). Она показывает, сколько единиц ресурсов нужно заданиям в очереди. Выбирайте эту опцию Масштабирование по умолчанию, если в кластере выполняется много относительно небольших заданий.
Другой вариант — масштабирование на основе метрики загрузки процессора (vCPU). Чтобы использовать его, отключите опцию Масштабирование по умолчанию и укажите целевой уровень загрузки vCPU.
Настроив подкластеры, нажмите кнопку Создать кластер.
Сервис запустит создание кластера. После того как статус кластера изменится на Running, вы сможете подключиться к любому активному подкластеру с помощью указанного в настройках SSH-ключа.
Task:
Подключение к кластеру и работа с Hive.
На этом уроке вы научитесь подключаться к кластеру Hadoop и работать с ним на примере выполнения запросов с помощью Hive.
Decision:
Подключимся к управляющему хосту главного подкластера. Поскольку хостам кластера Hadoop не назначается публичный IP-адрес, для подключения к ним нужна виртуальная машина, расположенная в той же сети Yandex Cloud.
Выберите машину, которую создавали раньше, или создайте новую. Подключитесь к ней по SSH. Вы уже делали это, когда изучали виртуальные машины.
Подключитесь с этой машины к хосту главного подкластера также с помощью SSH. Для этого на машине должна быть закрытая часть SSH-ключа, открытую часть которого вы указали при создании кластера Data Proc. Вы можете скопировать ключ на виртуальную машину или подключаться к ней с запущенным SSH-агентом.
Скопировать ключ можно с помощью утилиты nano. На виртуальной машине выполните команду:
sudo nano ~/.ssh/<имя ключа>
В открывшийся редактор скопируйте содержимое закрытой части SSH-ключа с вашей локальной машины.
Запустите SSH-агент:
eval `ssh-agent -s`
Добавьте ключ в список доступных агенту:
ssh-add ~/.ssh/<имя ключа>
Узнайте внутренний FQDN хоста главного подкластера. Для этого в консоли управления на странице кластера перейдите на вкладку Хосты и выберите хост с ролью MASTERNODE.
Откройте SSH-соединение с хостом Data Proc для пользователя root, например:
ssh root@<FQDN хоста>
Пошаговые инструкции по различным способам подключения к кластеру Data Proc приведены в документации.
Проверим, что команды Hadoop выполняются, например:
hadoop version
Результат выполнения этой команды выглядит так:
Запуск заданий Apache Hive
Как мы уже говорили ранее, Hive — это платформа для хранения данных и управления ими в экосистеме Hadoop. Она используется для доступа к большим датасетам, сохранённым в распределённом хранилище.
Hive позволяет работать с данными различного формата (csv, tsv, Parquet, ORC, Avro и другими), подключаться к БД и взаимодействовать с ней с помощью SQL-подобного языка запросов. Hive используется преимущественно для работы с данными в HDFS, HBase, S3-совместимых хранилищах и реляционных СУБД.
Запрос на действия с данными в Hive называется заданием. Задания можно запускать на управляющем хосте с помощью командной оболочки CLI Hive, а также с помощью CLI Yandex Cloud.
Для запуска Hive CLI выполните команду hive на управляющем хосте.
Проверьте, всё ли работает: выполните, например, команду select 1; — корректный результат выглядит так:
Теперь создайте внешнюю таблицу (external table) в формате Parquet, содержащую открытые данные о списке перелётов между городами США в 2018 году. Для этого с помощью Hive CLI выполните запрос:
hive> CREATE EXTERNAL TABLE flights (Year bigint, Month bigint, FlightDate string, Flight_Number_Reporting_Airline bigint, OriginAirportID bigint, DestAirportID bigint) STORED AS PARQUET LOCATION 's3a://yc-mdb-examples/dataproc/example01/set01';
Проверим список таблиц, выполнив команду show tables. Результат должен выглядеть так:
Запросим число перелётов с разбивкой по месяцам:
hive> SELECT Month, COUNT(*) FROM flights GROUP BY Month;
Пример результата такого запроса:
Безусловно, на одном примере сложно показать возможности сервиса Data Proc. Если вас интересует работа с большими данными в облаке, посмотрите доклады сотрудников Yandex Cloud об управлении кластерами Hadoop и заданиями в Data Proc на YouTube-канале Yandex Cloud.
Decision:
$ cat YOUR-KEY
$ eval `ssh-agent -s`
$ ssh-add YOUR-KEY
$ ssh root@<FQDN хоста>
$ hadoop version
$ hive
hive> select 1;
hive> CREATE EXTERNAL TABLE flights (Year bigint, Month bigint, FlightDate string, Flight_Number_Reporting_Airline bigint, OriginAirportID bigint, DestAirportID bigint) STORED AS PARQUET LOCATION 's3a://yc-mdb-examples/dataproc/example01/set01';
hive> show tables;
hive> SELECT Month, COUNT(*) FROM flights GROUP BY Month;
Task:
Создание датасетов, чартов и дашбордов.
На этом уроке вы научитесь создавать чарты и дашборды. Мы пройдём по всей цепочке сущностей DataLens начиная с источника данных.
Изучая ClickHouse, мы анализировали данные о погоде с помощью SQL-запросов.
Давайте посмотрим на примере того же самого набора данных, как с помощью DataLens быстро и наглядно показать отличия климата в Москве и Санкт-Петербурге.
Decision:
Источник данных.ClickHouse и DataLens интегрированы друг с другом, поэтому подключение DataLens к ClickHouse можно настроить всего за пару кликов.
В консоли управления запустите кластер ClickHouse, в котором развёрнута БД с таблицей Weather, созданной вами ранее. Перейдите на страницу кластера, на панели слева выберите DataLens.
Подключение. Нажмите кнопку Создать подключение. В открывшемся диалоговом окне вы увидите, что кластер ClickHouse, из которого мы возьмём данные для анализа, имя хоста и имя пользователя БД уже указаны.
Вам осталось только дать имя подключению в пустом поле вверху, ввести пароль к БД, нажать кнопку Проверить подключение и убедиться, что всё в порядке, а потом — кнопку Создать.
Датасет. После того как подключение будет создано, DataLens выведет на панели слева таблицы из БД и предложит создать датасет. Наш датасет будет состоять из одной таблицы: db1.Weather. Перетащите её на центральную панель, и внизу откроется предпросмотр данных.
Нажмите кнопку Сохранить и задайте имя датасета.
Подготовим данные. Это важная часть аналитической работы, и её не стоит пропускать. Прежде всего укажем имена полей на русском языке. Перейдите на вкладку Поля и переименуйте их:
- LocalDateTime - Дата и время
- LocalDate - Дата
- Month - Месяц
- Day - День
- TempC - Температура
- Pressure - Давление
- RelHumidity - Влажность
- Тип WindSpeed10MinAvg - Скорость ветра
- VisibilityKm - Видимость
- City - Город
Поля Дата и время, Дата, Месяц, День, Город будут полями-измерениями, а Температура, Давление, Влажность, Скорость ветра, Видимость — полями-показателями. Зададим для показателей тип агрегации Среднее.
Чарты. Приступим к созданию первого чарта. Нажмите кнопку Создать чарт. Выберите тип чарта Линейная диаграмма и перетащите Дата в раздел X , а Температура — в раздел Y.
На этом примере видно, что средства визуализации иногда помогают быстро проверить качество датасета: есть ли в нём пропущенные или странные, выбивающиеся из общей тенденции данные.
В нашем случае можно сделать вывод о том, что в датасете не хватает данных примерно с середины 2015-го по середину 2016-го.
Разделим показатели температуры для двух городов. Для этого перетащим Город в раздел Цвета. Кроме того, округлим значения поля Дата до месяцев, чтобы лучше увидеть, как различаются данные для Москвы и Санкт-Петербурга. Для этого слева от поля Дата нажмите зелёный значок календаря и в разделе Группировка выберите округление по месяцам.
Из этого графика уже можно делать выводы. В целом температура в Москве выше, чем в Санкт-Петербурге. Летом примерно на 5 градусов, зимой — на 1−2 градуса.
Сохраните чарт, чтобы затем использовать его для дашборда.
Чтобы окончательно разобраться с температурой, построим ещё один чарт — Столбчатую диаграмму — и сравним среднегодовую температуру. Выберите тип диаграммы. Добавьте поле Город в раздел X, чтобы разделить отображение значений температуры. Также для поля Дата выберите группировку по годам.
Кроме того, для чарта понадобится задать фильтр по датам. Поскольку мы сравниваем среднегодовые значения, неполные данные за 2009 и 2019 годы отбросим. В разделе Фильтры нажмите + и выберите поле Дата.
Для этого чарта мы возьмём только данные из диапазона с начала 2010-го по конец 2018-го. Нажмите кнопку Применить фильтр и сохраните чарт.
Сделайте сами два таких же чарта с данными о скорости ветра: линейную диаграмму со среднемесячными значениями скорости ветра в городах и столбчатую диаграмму со среднегодовыми значениями.
Теперь у нас достаточно чартов для информативного дашборда.
Дашборд. На панели слева выберите Дашборды и нажмите кнопку Создать дашборд. Введите название дашборда и нажмите Создать.
Если в каталоге это первый дашборд — он откроется сразу после создания. Если в каталоге есть другие дашборды, вы увидите список. В этом случае выберите из списка только что созданный дашборд.
Теперь добавим созданные нами чарты на дашборд. Нажмите Добавить и в выпадающем списке выберите Чарт. Поочерёдно выбирайте из списка и добавляйте чарты.
В результате на дашборде появятся четыре виджета с чартами. Меняйте размеры и положение виджетов для лучшей визуализации.
Осталось лишь несколько последних штрихов. В том же пункте меню Добавить создадим пару заголовков и селектор по датам. В правом верхнем углу каждого виджета нажмите значок шестерёнки, чтобы изменить названия. Сохраним дашборд.
То, какие чарты сделать и как их разместить на дашборде, бывает понятно не сразу. Рассмотрите несколько вариантов, когда строите дашборд, чтобы разобраться, какая именно визуализация лучше помогает ответить на вопросы.
В маркетплейсе DataLens вы найдёте ещё один дашборд с погодой. Он хорошо демонстрирует возможности визуализации данных этого сервиса.
Чтобы открыть публичный доступ к дашборду, справа от его названия нажмите ··· и выберите Публичный доступ. Скопируйте ссылку. По ней дашборд будет доступен всем, с любых устройств и без аутентификации.
Task:
Права доступа и роли для сервисного аккаунта.
В этом уроке вы научитесь работать с сервисными аккаунтами и назначать для них роли и права доступа к объектам.
В качестве объекта будет выступать созданный в сервисе KMS ключ шифрования.
Предположим, что перед вами стоит задача использовать сервисный аккаунт для ротации ключей.
Чтобы решить эту задачу, понадобится выполнить следующие шаги:
- Создать сервисный аккаунт.
- Получить права на управление этим сервисным аккаунтом.
- Создать статические ключи доступа и привязать их к сервисному аккаунту, чтобы он мог пройти авторизацию в сервисе KMS.
- Создать в сервисе KMS ключ шифрования и назначить сервисному аккаунту роль kms.admin для управления этим ключом.
- И, наконец, ротировать ключ, то есть создать его новую версию с такими же параметрами, из-под сервисного аккаунта.
- Нужно заметить, что через консоль управления сервисному аккаунту можно назначить роль только на каталог, в котором он был создан. Роли на все остальные ресурсы назначаются с помощью CLI или API. Поэтому для выполнения этой практической работы вам понадобится вспомнить, как пользоваться утилитой yc, чему вы уже научились в курсе «DevOps и автоматизация».
Decision:
ШАГ 1
Для начала создадим сервисный аккаунт. В консоли управления войдите в каталог облака, в котором вы будете выполнять эту практическую работу, и перейдите на вкладку Сервисные аккаунты. Нажмите кнопку Создать сервисный аккаунт.
В открывшемся окне задайте для нового сервисного аккаунта имя и, при желании, добавьте описание. Здесь аккаунту также можно добавить роли на каталог, в котором он создаётся.
Оставьте поле с ролями пустым и нажмите Создать.
ШАГ 2
Настройте для вашего аккаунта на Яндексе доступ на авторизацию под созданным сервисным аккаунтом.
Для начала убедитесь, что вы авторизованы в аккаунте с ролью admin. Чтобы это проверить, выполните команду
yc iam role list
Вы увидите список ролей вашего аккаунта. Примерно такой (роль admin должна в нем присутствовать):
Узнайте идентификатор своего аккаунта. Он понадобится, чтобы добавить вашему аккаунту роль editor на созданный сервисный аккаунт (сервисный аккаунт тоже является ресурсом, и для работы с ним нужна соответствующая роль). Воспользуйтесь для этого командой:
yc iam user-account get <имя_вашего_аккаунта>
Кроме того, нужно узнать идентификатор созданного сервисного аккаунта. Это можно сделать в разделе Сервисные аккаунты консоли управления. Выбрав нужный аккаунт в списке, вы перейдёте на страницу с детальной информацией о нем.
Теперь предоставьте вашему пользовательскому аккаунту права на управление созданным сервисным аккаунтом. Для этого нужно выполнить команду
yc iam service-account add-access-binding <ID_сервисного_аккаунта> \
--role editor --subject userAccount:<ID_пользовательского_аккаунта>
ШАГ 3
Настройте аутентификацию под сервисным аккаунтом с вашей машины.
Сначала нужно создать статические ключи доступа и сохранить их в json-файле (например key.json).
Воспользуйтесь для этого командой
yc --folder-name <имя_каталога> iam key create \
--service-account-name <имя_сервисного_аккаунта> --output key.json
После выполнения команды вы получите идентификатор созданной ключевой пары. Используя статические ключи доступа, можно получить IAM-токен для авторизации в сервисах.
Теперь нужно создать профиль, от имени которого будут выполняться операции из-под сервисного аккаунта. Для этого придумайте имя этого профиля (например yc-lab23-profile) и выполните команду:
yc config profile create <имя_профиля>
Привяжите к этому профилю ранее созданный статический ключ доступа с помощью команды:
yc config set service-account-key key.json
Чтобы убедиться, что всё сделано правильно, выведите информацию об авторизации и ключах доступа
yc config list
Вы должны получить примерно такой результат:
ШАГ 4
Теперь нужно создать ключ шифрования, ротацией которого вы будете управлять с помощью сервисного аккаунта. Для этого перейдите в дашборд каталога в консоли управления, нажмите кнопку Создать ресурс и выберите Ключ шифрования.
В открывшемся окне задайте для ключа имя и нажмите кнопку Создать. Новый ключ появится в списке в открывшемся разделе Ключи.
Чтобы назначить сервисному аккаунту роль для какого-либо ресурса, нужно знать идентификатор этого ресурса. Нажмите строку с созданным ключом, чтобы перейти на страницу детальной информации о нём, и скопируйте ID ключа.
Перейдем к назначению сервисному аккаунту роли kms.admin для управления созданным ключом шифрования. Перед этим нужно сначала вернуться в профиль вашего аккаунта.
yc config profile activate <имя_профиля>
Выполните команду:
yc --folder-name <имя_каталога> kms symmetric-key \
add-access-binding <ID_ключа_шифрования> --role kms.admin \
--subject serviceAccount:<ID_сервисного_аккаунта>
Теперь с помощью сервисного аккаунта вы можете управлять этим ключом шифрования.
ШАГ 5
В CLI переключитесь обратно в профиль сервисного аккаунта:
yc config profile activate <имя_профиля_сервисного_аккаунта>
Ротируйте ключ шифрования:
yc kms symmetric-key rotate <ID_ключа>
После выполнения команды перейдите на страницу детальной информации о ключе и откройте вкладку Операции.
Вы увидите, что операция по ротации ключа выполнена под вашим сервисным аккаунтом. Значит, всё получилось и задача решена!
Decision:
$ yc iam service-account create --name <имя_сервисного_аккаунта>
$ yc iam service-account create --name security-labs \
--description "Service account for Security course labs"
$ yc iam role list
$ yc iam user-account get <ваш_логин>
$ yc iam service-account list
$ yc iam service-account get <имя_сервисного_аккаунта>
$ yc iam service-account add-access-binding <id_сервисного_аккаунта> \
--role editor \
--subject userAccount:<id_пользовательского_аккаунта>
$ vim key.json
$ cat key.json
{
"id": "YOUR-ID1",
"service_account_id": "YOUR-ID2",
"created_at": "2023-12-09T01:19:35.625144946Z",
"key_algorithm": "RSA_2048",
"public_key": "-----BEGIN PUBLIC KEY-----\nYOUR-KEY1\n-----END PUBLIC KEY-----\n",
"private_key": "PLEASE DO NOT REMOVE THIS LINE! Yandex.Cloud SA Key ID <YOUR-ID1>\n-----BEGIN PRIVATE KEY-----\nYOUR-KEY2\n-----END PRIVATE KEY-----\n"
}
$ yc iam key create \
--service-account-name <имя_сервисного_аккаунта> \
--output key.json
$ yc config profile create <имя_профиля_сервисного_аккаунта>
$ yc config set service-account-key key.json
$ yc config set folder-id <идентификатор_рабочего_каталога>
$ yc config list
$ yc config profile list
$ yc config profile activate <имя_основного_профиля>
$ yc kms symmetric-key add-access-binding \
--id <id_ключа_шифрования> \
--role kms.admin \
--subject serviceAccount:<id_сервисного_аккаунта>
$ yc config profile activate <имя_профиля_сервисного_аккаунта>
$ yc kms symmetric-key rotate --id <id_ключа_шифрования>
Task:
Организация защищённого канала.
Защита данных, передаваемых между вашей локальной инфраструктурой и облаком, — важный элемент информационной безопасности. А удалённая работа, которая получила распространение в период пандемии коронавируса и сейчас закрепилась в практиках многих компаний, сделала эту задачу ещё более актуальной.
Чтобы защитить передаваемую информацию, используют VPN (Virtual Private Network) — технологию, позволяющую развернуть защищённое сетевое соединение «поверх» незащищённой сети (чаще всего это интернет). VPN-соединение представляет собой канал передачи данных между двумя узлами. Этот канал обычно называют VPN-туннелем. Если за одним из узлов находится целая сеть, то его называют VPN-шлюзом.
Механизм работы VPN:
- Перед созданием туннеля узлы идентифицируют друг друга, чтобы удостовериться, что шифрованные данные будут отправлены на нужный узел.
- На обоих узлах нужно заранее определить, какие протоколы будут использоваться для шифрования и обеспечения целостности данных.
- Узлы сверяют настройки, чтобы договориться об используемых алгоритмах. Если настройки разные, туннель не создаётся.
- Если сверка прошла успешно, то создаётся ключ, который используется для симметричного шифрования.
Этот механизм регламентируют несколько стандартов. Один из самых популярных — IPSec (Internet Protocol Security).
На этом уроке вы научитесь настраивать IPSec VPN-туннель между двумя VPN-шлюзами с помощью демона strongSwan. Один шлюз вы настроите на виртуальной машине в Yandex Cloud, второй — на своей локальной машине или виртуальной машине в другой облачной сети.
Decision:
Шаг 1. Создание ресурсов
Для практической работы вам понадобится сеть и подсеть в Yandex Cloud, а также созданная в этой подсети тестовая ВМ без публичного IP-адреса. Создайте эти ресурсы, если у вас их нет.
Теперь создадим IPSec-инстанс — ВМ, которая будет служить шлюзом для IPSec-туннеля. Чтобы это сделать:
Откройте ваш каталог, нажмите кнопку Создать ресурс и выберите пункт Виртуальная машина.
В поле Имя задайте имя ВМ, например ipsec-instance.
Выберите зону доступности, где находится подсеть, к которой будет подключён IPSec-инстанс, и тестовая ВМ.
В разделе Выбор образа/загрузочного диска перейдите в блок Cloud Marketplace и выберите образ IPSec-инстанс.
В блоке Сетевые настройки выберите нужную сеть, подсеть и назначьте ВМ публичный IP-адрес из списка или автоматически.
Важно использовать только статические публичные IP-адреса из списка или сделать IP-адрес ВМ статическим после её создания. Динамический IP-адрес может измениться после перезагрузки ВМ, и туннель перестанет работать.
В блоке Доступ укажите логин и SSH-ключ для доступа к ВМ.
Нажмите кнопку Создать ВМ.
Виртуальная машина готова.
Шаг 2. Настраиваем IPSec
Теперь настроим шлюз с публичным IP-адресом, который будет устанавливать IPSec-соединение с удалённым шлюзом (вашей локальной машиной или ВМ в другой облачной сети).
Вы можете создать в своём каталоге ещё одну облачную сеть с подсетью, создать в ней IPSec-инстанс из образа и использовать его в качестве удалённого шлюза. Либо можно использовать в качестве шлюза машину в вашей локальной сети. Вам понадобится публичный IP-адрес удалённого шлюза и CIDR подсети.
Допустим, публичный IP-адрес вашего шлюза — 130.193.32.25, а за ним находится подсеть c префиксом подсети CIDR 10.128.0.0/24. Шлюз будет устанавливать IPSec-соединение с удалённым шлюзом с IP-адресом, например, 1.1.1.1, за которым находится подсеть с префиксом подсети CIDR 192.168.0.0/24.
Подключитесь к ВМ IPSec-инстанс по SSH:
ssh <имя пользователя>@130.193.32.25
Откройте конфигурацию IPSec:
sudo nano /etc/ipsec.conf
В разделе config setup файла конфигурации задайте следующие параметры:
config setup
charondebug="all"
uniqueids=yes
strictcrlpolicy=no
Добавьте новый раздел с описанием тестового соединения, например conn cloud-to-hq.
Задайте параметры тестового соединения:
leftid — публичный IP-адрес IPSec-инстанса.
leftsubnet — CIDR подсети, к которой подключён IPSec-инстанс.
right — публичный IP-адрес шлюза на другом конце VPN-туннеля.
rightsubnet — CIDR подсети, к которой подключён VPN-шлюз на другом конце VPN-туннеля.
Параметры ike и esp — это алгоритмы шифрования, которые поддерживаются на удалённом шлюзе. Перечень поддерживаемых алгоритмов можно посмотреть на сайте strongSwan: IKEv1 и IKEv2.
Укажите остальные настройки, консультируясь с документацией strongSwan и учитывая настройки удалённого шлюза.
У вас должна получиться примерно такая конфигурация:
conn cloud-to-hq
authby=secret
left=%defaultroute
leftid=130.193.32.25
leftsubnet=10.128.0.0/24
right=1.1.1.1
rightsubnet=192.168.0.0/24
ike=aes256-sha2_256-modp1024!
esp=aes256-sha2_256!
keyingtries=0
ikelifetime=1h
lifetime=8h
dpddelay=30
dpdtimeout=120
dpdaction=restart
auto=start
Сохраните изменения и закройте файл.
Откройте файл /etc/ipsec.secrets и укажите в нём пароль для установки соединения:
130.193.32.25 1.1.1.1 : PSK "<пароль>"
Перезапустите strongSwan:
sudo systemctl restart strongswan-starter
Шаг 3. Настраиваем статическую маршрутизацию
Теперь нужно настроить маршрутизацию между IPSec-инстансом и тестовой ВМ без публичного IP-адреса. Для этого создадим таблицу маршрутизации и добавим в неё статические маршруты.
Откройте сервис Virtual Private Cloud в каталоге, где требуется создать статический маршрут.
Выберите раздел Таблицы маршрутизации в панели слева и нажмите кнопку Создать таблицу маршрутизации.
Задайте имя таблицы маршрутизации, выберите сеть, в которой требуется её создать, и нажмите кнопку Добавить маршрут.
В открывшемся окне введите префикс подсети назначения на удалённой стороне, в примере это 192.168.0.0/24.
В поле Next hop укажите внутренний IP-адрес IPSec-шлюза и нажмите кнопку Добавить.
Нажмите кнопку Создать таблицу маршрутизации.
Чтобы использовать статические маршруты, нужно привязать таблицу маршрутизации к подсети. Для этого в разделе Подсети, в строке нужной подсети, нажмите кнопку … и в открывшемся меню выберите пункт Привязать таблицу маршрутизации.
В открывшемся окне выберите созданную таблицу и нажмите кнопку Привязать. Созданный маршрут можно применять и к другим подсетям этой сети.
Шаг 4. Настраиваем IPSec на другом шлюзе
Для работы VPN-туннеля нужно настроить второй шлюз.
Настройте strongSwan аналогично первому IPSec-шлюзу, но с зеркальными настройками IP-адресов и подсетей в файле /etc/ipsec.conf. Должна получиться такая конфигурация:
conn hq-to-cloud
authby=secret
left=%defaultroute
leftid=1.1.1.1
leftsubnet=192.168.0.0/24
right=130.193.32.25
rightsubnet=10.128.0.0/24
ike=aes256-sha2_256-modp1024!
esp=aes256-sha2_256!
keyingtries=0
ikelifetime=1h
lifetime=8h
dpddelay=30
dpdtimeout=120
dpdaction=restart
auto=start
Укажите пароль для соединения в файле /etc/ipsec.secrets, указав IP-адреса шлюзов в обратном порядке:
1.1.1.1 130.193.32.25 : PSK "<пароль>"
Перезапустите strongSwan:
sudo systemctl restart strongswan-starter
Шаг 5. Проверяем, что всё работает
Чтобы убедиться, что туннель между шлюзами установлен, выполните на любом из шлюзов команду:
sudo ipsec status
Если всё в порядке, то у вас должно появиться примерно такое сообщение:
Security Associations (1 up, 0 connecting):
hq-to-cloud[3]: ESTABLISHED 29 minutes ago, 10.128.0.26[130.193.33.12]...192.168.0.23[1.1.1.1]
hq-to-cloud{3}: INSTALLED, TUNNEL, reqid 3, ESP in UDP SPIs: c7fa371d_i ce8b91ad_o
hq-to-cloud{3}: 10.128.0.0/24 === 192.168.0.0/24
Статус ESTABLISHED означает, что туннель между шлюзами создан.
Сведения об установке и работе соединения находятся в логах strongSwan. Просмотреть логи можно с помощью команды:
sudo journalctl -u strongswan-starter
Проверить статус демона strongSwan можно командой:
systemctl status strongswan-starter
Осталось проверить связность соединения. Для этого создайте ещё одну тестовую виртуальную машину за вторым шлюзом, а затем пропингуйте одну тестовую машину с другой.
Decision:
$ ssh <имя пользователя>@130.193.32.25
$ sudo vim /etc/ipsec.conf
$ sudo cat /etc/ipsec.conf
...
config setup
charondebug="all"
uniqueids=yes
strictcrlpolicy=no
...
conn cloud-to-hq
authby=secret
left=%defaultroute
leftid=130.193.32.25
leftsubnet=10.128.0.0/24
right=1.1.1.1
rightsubnet=192.168.0.0/24
ike=aes256-sha2_256-modp1024!
esp=aes256-sha2_256!
keyingtries=0
ikelifetime=1h
lifetime=8h
dpddelay=30
dpdtimeout=120
dpdaction=restart
auto=start
$ sudo vim /etc/ipsec.secrets
$ sudo cat /etc/ipsec.secrets
130.193.32.25 1.1.1.1 : PSK "<пароль>"
$ sudo systemctl restart strongswan-starter
$ exit
$ ssh <имя пользователя>@130.193.32.26
$ sudo vim /etc/ipsec.conf
$ sudo cat /etc/ipsec.conf
...
config setup
charondebug="all"
uniqueids=yes
strictcrlpolicy=no
...
conn hq-to-cloud
authby=secret
left=%defaultroute
leftid=1.1.1.1
leftsubnet=192.168.0.0/24
right=130.193.32.25
rightsubnet=10.128.0.0/24
ike=aes256-sha2_256-modp1024!
esp=aes256-sha2_256!
keyingtries=0
ikelifetime=1h
lifetime=8h
dpddelay=30
dpdtimeout=120
dpdaction=restart
auto=start
$ sudo vim /etc/ipsec.secrets
$ sudo cat /etc/ipsec.secrets
1.1.1.1 130.193.32.25 : PSK "<пароль>"
$ sudo systemctl restart strongswan-starter
$ ssh <имя пользователя>@130.193.32.25
$ sudo ipsec status
$ sudo journalctl -u strongswan-starter
$ systemctl status strongswan-starter
Task:
Выпуск сертификата для сайта.
В этой практической работе мы зарегистрируем домен, привяжем его к бакету в объектном хранилище и настроим для этого домена автоматический выпуск сертификата с помощью Certificate Manager.
Decision:
Шаг 1
Если у вас нет своего домена, зарегистрируйте временный домен, например, на сайте freenom.com:
Проверьте на сайте доступность имени, которое вы придумали для своего домена.
Введите имя вместе с доменом верхнего уровня, например testpracticum2022.ml, иначе при попытке зарезервировать домен сервис будет сообщать, что домен занят.
Если это имя доступно, добавьте домен в корзину и укажите свой email для подтверждения.
Проверьте почту и подтвердите регистрацию домена.
Обновите страницу с заказом.
После подтверждения регистрации домена зайдите в объектное хранилище (Object Storage) и создайте новый публичный бакет. Его название должно совпадать с полным названием домена.
Переключите доступ на чтение объектов в Публичный. Загрузите в бакет файлы статического сайта (вы можете воспользоваться файлами из практической работы курса «Хранение и анализ данных».
Выберите на панели управления раздел Веб-сайт, переключите бакет в режим Хостинг и нажмите Сохранить.
Шаг 2
Настроить защищённый доступ к бакету можно двумя способами: загрузить сертификат прямо в бакет или с помощью Certificate Manager. Воспользуемся вторым способом.
В консоли управления перейдите в сервис Certificate Manager. Для выпуска сертификата с помощью этого сервиса подтвердите владение доменом: в разделе Сертификаты нажмите кнопку Добавить сертификат и выберите Сертификат Let’s Encrypt.
В открывшемся окне задайте имя создаваемого сертификата и заполните поле с именем вашего домена. Нажмите кнопку Создать.
Сервис автоматически направит запрос на создание сертификата, а домен перейдёт в статус проверки.
Для выпуска сертификата необходимо подтвердить статус владения доменом. Откройте страницу с деталями запроса на сертификат:
На этой странице для нас важны два поля: имя DNS-записи и её значение. Если вы создавали домен на freenom.com, то перейдите в личный кабинет на этом сайте, выберите раздел Services → My Domains и нажмите кнопку Manage Domains:
Выберите Manage Freenom DNS:
В открывшемся редакторе добавьте TXT-запись для подтверждения владения доменом. В качестве названия записи задайте _acme-challenge без полного названия домена. В качестве значения TXT-записи — значение со страницы проверки прав на домен в консоли управления Yandex Cloud.
Аналогично внесите значение CNAME-записи со страницы проверки прав на домен в консоли управления Yandex Cloud.
Добавьте также запись CNAME для привязки поддомена WWW к вашему бакету:
В поле Target укажите полное имя бакета, включая .website.yandexcloud.net. Сохраните сделанные изменения.
Если вы используете собственный домен, задайте параметры DNS в настройках вашего DNS-сервера. Для применения настроек DNS потребуется некоторое время — обычно до 15 минут.
После окончания проверки домена Certificate Manager автоматически выпустит сертификат.
Шаг 3
Теперь настроим доступ к сайту, то есть к созданному бакету, по протоколу HTTPS с помощью сертификата. Для этого перейдите в раздел HTTPS и нажмите кнопку Настроить.
В поле Источник выберите Certificate Manager, в поле Сертификат — ранее выпущенный сертификат. Нажмите кнопку Сохранить.
Теперь ваш сайт доступен по протоколу HTTPS. Чтобы проверить это, откройте его в браузере. В адресной строке браузера должен отображаться значок защищённого соединения.
Task:
Создание и ротация ключей шифрования
На прошлом уроке вы познакомились с возможностями сервиса управления ключами шифрования KMS. В этой практической работе вы научитесь создавать ключи шифрования и управлять ими, а также использовать эти ключи для шифрования и расшифрования данных.
Decision:
Шаг 1
Перейдите в панель управления Yandex Cloud, нажмите кнопку Создать ресурс и выберите из выпадающего списка пункт Ключ шифрования.
Задайте для создаваемого ключа имя (например yc-lab-key1), заполните поле Описание (это необязательно) и выберите алгоритм шифрования. Предположим, что ключ нужно ротировать каждый день. Для этого в поле Период ротации, дни выберите вариант Своё значение и введите число 1 в поле справа.
Нажмите кнопку Создать. Когда операция создания ключа завершится, новый ключ появится в списке.
Нажав на строку с ключом, вы перейдёте на страницу детальной информации. На ней приведены все параметры ключа, а также список его версий. Обратите внимание, что ID (идентификатор) ключа и ID конкретной версии ключа отличаются. Важно их не путать.
Шаг 2
Давайте используем созданный ключ для шифрования и расшифрования данных. Создайте у себя на диске файл (например, текстовый файл с именем plain.txt). Добавьте в него любой текст и сохраните содержимое. Напомним, что размер файла не должен превышать 32 килобайта.
Запустите утилиту командной строки (bash или cmd) и перейдите в каталог с файлом plain.txt. Зашифруйте этот файл с помощью утилиты yc, а результат операции шифрования выведите в файл encrypted.txt. Для этого выполните команду:
yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted.txt
После выполнения команды будет создан файл encrypted.txt, который содержит зашифрованный текст. Утилита yc также выведет информацию о том, каким ключом и какой его версией файл был зашифрован.
Шаг 3
Теперь расшифруйте этот файл, а результат операции выведите в файл decrypted.txt. Для этого выполните команду:
yc kms symmetric-crypto decrypt --id <ID ключа> --ciphertext-file encrypted.txt --plaintext-file decrypted.txt
В результате выполнения команды будет создан файл decrypted.txt с идентичным исходному файлу (plain.txt) содержимым.
Если расшифровать файл не удалось, утилита выдаст сообщение об ошибке.
Шаг 4
Создайте новую версию ключа. Для этого перейдите на страницу детальной информации о ключе и нажмите кнопку Ротировать. Новая версия ключа появится в списке версий и станет основной (Primary). Обратите внимание, что идентификаторы версий отличаются друг от друга.
Шаг 5
Запланируйте удаление первой версии ключа. Для этого в списке версий нажмите на значок … в строке с этой версией, а затем выберите пункт Запланировать удаление.
В появившемся окне установите время, по истечении которого ключ будет удалён, и нажмите Запланировать. Версия ключа не может быть удалена моментально, минимальный период времени для её удаления составляет один день.
После этого в списке версий удаляемый ключ будет помечен как запланированный на удаление (Scheduled For Destruction). Теперь этой версией ключа невозможно расшифровать файлы, которые были зашифрованы с её помощью.
Провести ротацию ключа можно и из командной строки. Для этого используется команда:
yc kms symmetric-key rotate <ID ключа>
Шаг 6
Зашифруйте исходный файл plain.txt с помощью новой версии ключа. Результат запишите в файл encrypted_with_new_key.txt.
yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted_with_new_key.txt
Теперь у вас есть два файла:
- encrypted.txt, зашифрованный версией ключа, которая помечена на удаление;
- encrypted_with_new_version.txt, зашифрованный новой версией ключа.
Попробуйте расшифровать данные из обоих файлов. Вы увидите, что расшифровать первый файл не получилось, а файл, который зашифрован второй версией ключа, расшифрован.
Запланированное удаление первой версии ключа можно отменить. Это позволит расшифровать данные из первого файла.
В строке версии ключа, которая запланирована на удаление, нажмите значок …, а затем кнопку кнопку Отменить удаление. Эта версия снова получит статус активной. Проверьте, что она работает, расшифровав файл encrypted.txt.
Decision:
$ vim plain.txt
$ cat plain.txt
test text
$ yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted.txt
$ yc kms symmetric-crypto encrypt --id abjkh5a8k2uao3f8qi8k --plaintext-file plain.txt --ciphertext-file encrypted.txt
$ cat encrypted.txt
abj2vjcrv89d83cef26in#���k��|�V�X�
����:�F���*Y��l�(���у��֜�l�S
$ yc kms symmetric-crypto decrypt --id <ID ключа> --ciphertext-file encrypted.txt --plaintext-file decrypted.txt
$ cat decrypted.txt
test text
$ yc kms symmetric-key rotate <ID ключа>
$ yc kms symmetric-crypto encrypt --id <ID ключа> --plaintext-file plain.txt --ciphertext-file encrypted_with_new_key.txt
$ cat encrypted_with_new_key.txt
abjofv85g302tc8mjhqq��n�nߊz6�5�A�
�)ڀi�R0���лo��s �^
�m�C zci�b�'
Decision:
Защитил диплом о дополнительной переподготовке:
2011-09-01 - 2018-05-30: Иркутский государственный университет, Иркутск. Должность: Информационные технологии и телекоммуникационные системы - Бакалавр / Электроника и наноэлектроника - Магистр / Информационная безопасность - Дополнительное образование. Дополнительная информация: Навыки - Html, Css, Windows, Виртуализация, Linux, Sql, Bash, Clouds, Python, Спутниковые радионавигационные системы, С++. Достижения: Разработал сайт портфолио, куда публиковал все решенные мной интересные задачи, отчеты лабораторных работ и презентации, сверстал простой сайт.
Show
Цель:
# Сверстать сайт портфолио.
# Установить виртуальный сервер для дальнейшего тестирования сайта и веб-сервера.
# Установить и настроить веб-сервер LAMP.
# Разработать базу данных Портфолио.
# Написать Sql запрос.
# Подготовить хостинг и настроить облачный сервер.
# Перенести базу данных c тестовой виртуаььной машины в облачный сервер.
# Установить и настроить веб-сервер Django.
# Перенести базу данных c Sqlite в Postgresql.
# Разработать сайт с админ панелью для управления контентами сайта.
# Установить и настроить веб-сервер React.js для frontend-разработки.
Skills:
# Верстка Html-страниц.
# Администрирование веб-сервера LAMP.
# Администрирование баз данных.
# Разработка баз данных.
# Миграция базы данных.
# Администрирование веб-сервера Django.
# Разработка сайта на Django.
Task:
Верстка Html-страниц.
# Верстка Html-страниц.
Decision:
Ссылка на репозиторий: github.com
Мой сайт: dato138it.ru
Task:
Установка Apache2.
# Администрирование Веб-сервера LAMP.
Decision:
root@kvmubuntu:~# apt install apache2
root@kvmubuntu:~# ufw app list
Available applications:
Apache
Apache Full
Apache Secure
OpenSSH
root@kvmubuntu:~# ufw allow 80
root@kvmubuntu:~# ufw status
Status: active
To Action From
...
80 ALLOW Anywhere
80 (v6) ALLOW Anywhere (v6)
tuser@kvmubuntu:~$ google-chrome http://tipubuntu:80
Task:
Установка и настройка Mysql.
# Администрирование баз данных.
Decision:
root@kvmubuntu:~# apt install mysql-server
root@kvmubuntu:~# mysql
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'tpassword';
mysql> exit
root@kvmubuntu:~# mysql_secure_installation
...
Press y|Y for Yes, any other key for No: y
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1
Change the password for root ? ((Press y|Y for Yes, any other key for No) : n
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : n
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
root@kvmubuntu:~# mysql -u root -p
mysql> SELECT user,authentication_string,plugin,host FROM mysql.user;
mysql> rename user 'root'@'localhost' to 'tuser'@'localhost';
mysql> create database tbase;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| tbase |
...
+--------------------+
mysql> exit
Task:
Добавление учетной записи к базе данных.
# Администрирование баз данных.
Decision:
mysql> CREATE USER 'tuser'@'%' IDENTIFIED BY 'tpassword';
mysql> GRANT ALL ON tbase.* TO 'tuser'@'%';
mysql> exit
tuser@kvmubuntu:~$ mysql -u tuser -p
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| tbase |
+--------------------+
3 rows in set (0.07 sec)
Task:
Настройка Lamp.
# Администрирование Веб-сервера LAMP.
Decision:
root@kvmubuntu:~# apt install php libapache2-mod-php php-mysql
root@kvmubuntu:~# php -v
PHP 8.1.2-1ubuntu2.18 (cli) (built: Jun 14 2024 15:52:55) (NTS)
...
root@kvmubuntu:~# vim /etc/php/8.1/apache2/php.ini
root@kvmubuntu:~# cat /etc/php/8.1/apache2/php.ini | grep upload_max_filesize
...
upload_max_filesize = 32M
post_max_size = 48M
memory_limit = 256M
max_execution_time = 600
max_input_vars = 3000
max_input_time = 1000
root@kvmubuntu:~# service apache2 restart
root@kvmubuntu:~# mkdir /var/www/dato138it
root@kvmubuntu:~# chown -R $USER:$USER /var/www/dato138it
root@kvmubuntu:~# vim /etc/apache2/sites-available/dato138it.conf
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf
<VirtualHost *:80>
ServerName dato138it
ServerAlias www.dato138it
ServerAdmin tmail138@mail.ru
DocumentRoot /var/www/dato138it
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
root@kvmubuntu:~# a2ensite dato138it
root@kvmubuntu:~# a2dissite 000-default
root@kvmubuntu:~# apache2ctl configtest
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# vim /var/www/dato138it/index.html
root@kvmubuntu:~# cat /var/www/dato138it/index.html
<html>
<head>
<title>dato138it website</title>
</head>
<body>
<h1>Hello World!</h1>
<p>This is the landing page of <strong>dato138it</strong>.</p>
</body>
</html>
root@kvmubuntu:~# google-chrome http://tipubuntu:80
root@kvmubuntu:~# cat /etc/apache2/mods-enabled/dir.conf
<IfModule mod_dir.c>
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
</IfModule>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# vim /var/www/dato138it/info.php
root@kvmubuntu:~# cat /var/www/dato138it/info.php
<?php
phpinfo();
root@kvmubuntu:~# google-chrome http://tipubuntu:80/info.php
Task:
Разработать схему БД.
# Разработка баз данных.
Decision:
Task:
Создание таблиц.
# Разработка баз данных.
Decision:
mysql> create table tbase.ttable1
(ttable1_id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(255));
CREATE TABLE tbase.ttable1
(ttable1_id INT PRIMARY KEY AUTO_INCREMENT,
column1 VARCHAR(255),
column2 VARCHAR(255),
column3 VARCHAR(255),
column4 VARCHAR(255),
column5 Date,
column6 Date);
mysql> INSERT INTO tbase.ttable1
(column1, column2)
VALUES
("text1","text2"),
("text3","text4"),
("text5","text6"),
("text7","text8");
mysql> select * from tbase.ttable1;
+------------+---------+---------+---------+---------+---------+---------+
| ttable1_id | column1 | column2 | column3 | column4 | column5 | column6 |
+------------+---------+---------+---------+---------+---------+---------+
| 1 | text1 | text2 | NULL | NULL | NULL | NULL |
| 2 | text3 | text4 | NULL | NULL | NULL | NULL |
| 3 | text5 | text6 | NULL | NULL | NULL | NULL |
| 4 | text7 | text8 | NULL | NULL | NULL | NULL |
+------------+---------+---------+---------+---------+---------+---------+
4 rows in set (0.00 sec)
mysql> CREATE TABLE tbase.ttable2
(ttable2_id INT PRIMARY KEY AUTO_INCREMENT,
columnId INT,
column1 VARCHAR(500),
FOREIGN KEY (columnId) REFERENCES tbase.ttable1 (ttable1_id));
mysql> INSERT INTO tbase.ttable2
(columnId, column1)
VALUES
(1, "text9"),
(1, "text10"),
(2, "text11"),
(3, "text12"),
(4, "text13");
mysql> select * from tbase.ttable2;
+------------+----------+---------+
| ttable2_id | columnId | column1 |
+------------+----------+---------+
| 1 | 1 | text9 |
| 2 | 1 | text10 |
| 3 | 2 | text11 |
| 4 | 3 | text12 |
| 5 | 4 | text13 |
+------------+----------+---------+
5 rows in set (0.00 sec)
Task:
Выборка данных из двух таблиц.
# Разработка баз данных.
Decision:
mysql> select * from tbase.ttable2
inner join tbase.ttable1
on ttable1.ttable1_id = ttable2.columnId;
+------------+----------+---------+------------+---------+---------+---------+---------+---------+---------+
| ttable2_id | columnId | column1 | ttable1_id | column1 | column2 | column3 | column4 | column5 | column6 |
+------------+----------+---------+------------+---------+---------+---------+---------+---------+---------+
| 1 | 1 | text9 | 1 | text1 | text2 | NULL | NULL | NULL | NULL |
| 2 | 1 | text10 | 1 | text1 | text2 | NULL | NULL | NULL | NULL |
| 3 | 2 | text11 | 2 | text3 | text4 | NULL | NULL | NULL | NULL |
| 4 | 3 | text12 | 3 | text5 | text6 | NULL | NULL | NULL | NULL |
| 5 | 4 | text13 | 4 | text7 | text8 | NULL | NULL | NULL | NULL |
+------------+----------+---------+------------+---------+---------+---------+---------+---------+---------+
5 rows in set (0.00 sec)
mysql> exit
Source:
# https://metanit.com/sql/mysql/2.5.php
# https://metanit.com/sql/mysql/5.2.php
Task:
Настройка Php.
# Администрирование веб-сервера LAMP.
Decision:
root@kvmubuntu:~# vim /var/www/dato138it/index.php
root@kvmubuntu:~# cat /var/www/dato138it/index.php
<?php
$user = "tuser";
$password = "tpassword";
$database = "tbase";
$table1 = "ttable1";
$table2 = "ttable2";
try {
$db = new PDO("mysql:host=localhost;dbname=$database", $user, $password);
$query = $db->query("
select * from $database.$table2
inner join $database.$table1
on $table1.experience_id=$table2.experienceId;
");
foreach($query as $row) {
echo '
<li><strong>
' . $row['column1'] . '
-
' . $row['column2'] . '
:</strong>
' . $row['column3'] . '
;
' . $row['column4'] . '
;
' . $row['column5'] . '
;
' . $row['column6'] . '
</li>
';
}
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
root@kvmubuntu:~# google-chrome http://tipubuntu/index.php
Task:
Настройка Phpmyadmin.
# Администрирование веб-сервера LAMP.
Decision:
root@kvmubuntu:~# apt install phpmyadmin php-mbstring php-zip php-gd php-json php-curl
...
Configure database for phpmyadmin with dbconfig-common? [yes/no] y
granting access to database phpmyadmin for phpmyadmin@localhost: failed.
error encountered creating user:
mysql said: ERROR 1819 (HY000) at line 1: Your password does not satisfy the current policy requirements
An error occurred while installing the database:
mysql said: ERROR 1819 (HY000) at line 1: Your password does not satisfy the current policy requirements . Your options are:
* abort - Causes the operation to fail; you will need to downgrade,
reinstall, reconfigure this package, or otherwise manually intervene
to continue using it. This will usually also impact your ability to
install other packages until the installation failure is resolved.
* retry - Prompts once more with all the configuration questions
...
1. abort 2. retry 3. retry (skip questions) 4. ignore
Next step for database installation: 1
...
Errors were encountered while processing:
phpmyadmin
needrestart is being skipped since dpkg has failed
E: Sub-process /usr/bin/dpkg returned an error code (1)
root@kvmubuntu:~# mysql -u tuser -p
mysql> UNINSTALL COMPONENT "file://component_validate_password";
mysql> exit
root@kvmubuntu:~# apt install phpmyadmin
root@kvmubuntu:~# apt install php8.1-fpm php8.1 libapache2-mod-php8.1 php8.1-common php8.1-mysql php8.1-xml php8.1-xmlrpc php8.1-imagick php8.1-cli php8.1-imap php8.1-opcache php8.1-soap php8.1-intl php8.1-bcmath unzip
root@kvmubuntu:~# mysql -u tuser -p
mysql> INSTALL COMPONENT "file://component_validate_password";
mysql> exit
root@kvmubuntu:~# phpenmod mbstring
root@kvmubuntu:~# systemctl restart apache2
root@kvmubuntu:~# cp /etc/phpmyadmin/apache.conf /etc/apache2/conf-available/phpmyadmin.conf
root@kvmubuntu:~# a2enconf phpmyadmin.conf
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# google-chrome http://tipubuntu/phpmyadmin
Source:
# https://bozza.ru/art-260.html
# https://www.digitalocean.com/community/tutorials/how-to-install-linux-apache-mysql-php-lamp-stack-on-ubuntu-22-04
# https://losst.pro/ustanovka-chrome-v-ubuntu-18-04?ysclid=lmdflkvc3v463974954
# https://qna.habr.com/q/439469?ysclid=lmdgkb49q827401606
# https://steptuser.org/course/63054/syllabus
# https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-phpmyadmin-on-ubuntu-20-04
# https://serverspace.ru/support/help/osnovnye-komandy-ufw/
# https://www.tutsmake.com/how-to-install-lamp-apache-mysql-php-in-ubuntu-22-04/
# https://www.php.net/manual/ru/faq.html.php
# https://www.8host.com/blog/kak-rabotayut-stroki-v-php/
# https://www.youtube.com/watch?v=FxwPQkP3OGY&t=611s
# https://timeweb.com/ru/community/articles/kak-ustanovit-stek-lamp-na-ubuntu-20-04
# https://wiki.merionet.ru/articles/perenos-bazy-dannyx-mysql-so-starogo-na-novyj-server
Task:
Настройка приложения Django.
# Администрирование веб-сервера Django.
Decision:
root@kvmubuntu:~# apt install python3.10-venv
root@kvmubuntu:~# python3 -m venv djangoenv
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
Django==5.0.2
djangorestframework==3.14.0
django-ckeditor-5==0.2.12
psycopg2-binary==2.9.9
django-ckeditor==6.7.1
python-decouple==3.8
root@kvmubuntu:~# source djangoenv/bin/activate
root@kvmubuntu:~# pip install -r requirements.txt
root@kvmubuntu:~# pip list
Package Version
------------------- -------
asgiref 3.8.1
Django 5.0.2
django-ckeditor 6.7.1
django-ckeditor-5 0.2.12
django-js-asset 2.2.0
djangorestframework 3.14.0
pillow 10.4.0
pip 22.0.2
psycopg2-binary 2.9.9
pytz 2024.1
setuptools 59.6.0
sqlparse 0.5.1
typing_extensions 4.12.2
Task:
Миграция БД с Sqlite3 на PostgreSQL.
# Миграция базы данных.
Decision:
root@kvmubuntu:~# pip install --upgrade pip
root@kvmubuntu:~# pip install --upgrade setuptools wheel
root@kvmubuntu:~# apt-get install libpq-dev python3-dev
root@kvmubuntu:~# pip install psycopg2-binary==2.9.9
root@kvmubuntu:~# python dato138it/manage.py dumpdata > datadump.json
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
DATABASES = {
'default': {
#'ENGINE': 'django.db.backends.sqlite3',
#'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'tbase',
'USER': 'tuser',
'PASSWORD': 'tpassword',
'HOST': 'tipubuntu',
'PORT': '5432',
}
}
...
root@kvmubuntu:~# python dato138it/manage.py migrate --run-syncdb
...
django.core.exceptions.ImproperlyConfigured: 'django.db.backends.potgresql_psycopg2' isn't an available database backend or couldn't be imported. Check the above exception. To use one of the built-in backends, use 'django.db.backends.XXX', where XXX is one of:
'mysql', 'oracle', 'postgresql', 'sqlite3'
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
DATABASES = {
'default': {
...
'ENGINE': 'django.db.backends.postgresql',
...
}
}
...
root@kvmubuntu:~# python dato138it/manage.py migrate --run-syncdb
root@kvmubuntu:~# python dato138it/manage.py shell
>>> from django.contrib.contenttypes.models import ContentType
>>> ContentType.objects.all().delete()
>>> quit()
root@kvmubuntu:~# python dato138it/manage.py loaddata datadump.json
root@kvmubuntu:~# python dato138it/manage.py runserver
Source:
# https://www.youtube.com/watch?v=pQzKHNozHts - Миграция с Sqlite3 на PostgreSQL.
Task:
Миграция БД PostgreSQL с помощью дампа.
# Миграция базы данных.
Decision:
root@kvmubuntu:~# pg_dump -Fc -v --username=tuser --dbname=tbase -f datapsql.dump
root@aw:/# scp datapsql.dump tuser@tipubuntu:/home/tuser/
root@kvmubuntu:~# pg_restore -v --no-owner --port=5432 --username=tuser --dbname=tbase datapsql.dump
root@kvmubuntu:~# psql -U tuser -d tbase -h tipubuntu
tbase=# \d
Source:
# https://procloud.ru/blog/cases/perenos-bazy-dannykh-postgresql-s-pomoshchyu-dampa-i-ee-vosstanovlenie/ - Перенос базы данных PostgreSQL с помощью дампа и ее восстановление.
# https://losst.pro/kopirovanie-fajlov-scp - Копирование файлов scp.
Task:
Применить изменения в проекте Django с момента бэкапа. Запуск проекта.
# Администрирование веб-сервера Django.
Decision:
root@kvmubuntu:~# python dato138it/manage.py makemigrations
root@kvmubuntu:~# python dato138it/manage.py migrate
root@kvmubuntu:~# python dato138it/manage.py runserver tipubuntu:8000 &
root@kvmubuntu:~# google-chrome http://tipubuntu:8000
Source:
# https://pocoz.gitbooks.io/django-v-primerah/content/sozdanie-i-primenenie-migracij.html - Создание и применение миграций.
Task:
Первый запуск проекта Django.
# Разработка сайта на Django.
Decision:
root@aw:/# mkdir drf
root@aw:/# cd drf/
root@aw:/# django-admin startproject dato138it
root@aw:/# cd dato138it/
root@aw:/# google-chrome http://127.0.0.1:8000 &
root@aw:/# python3 manage.py runserver
root@aw:/# python3 manage.py migrate
root@aw:/# vim settings.py
root@aw:/# cat settings.py
...
#LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ru'
#TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Moscow'
...
root@aw:/# python3 manage.py startapp portfolio
root@aw:/# cat settings.txt
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portfolio.apps.PortfolioConfig',
]
...
root@aw:/# vim portfolio\models.py
root@aw:/# cat portfolio\models.py
from django.db import models
class Portfolio(models.Model):
title=models.CharField(max_length=255, verbose_name="Заголовок")
content=models.TextField(blank=True, verbose_name="Текст статьи")
time_create=models.DateTimeField(auto_now_add=True, verbose_name="Время создания")
time_update=models.DateTimeField(auto_now=True, verbose_name="Время изменения")
is_published=models.BooleanField(default=True, verbose_name="Публикация")
cat=models.ForeignKey('Category', on_delete=models.PROTECT, null=True, verbose_name="Категории")
def __str__(self):
return self.title
class Category(models.Model):
name=models.CharField(max_length=100, db_index=True, verbose_name="Категория")
def __str__(self):
return self.name
root@aw:/# python3 manage.py makemigrations
root@aw:/# python3 manage.py migrate
root@aw:/# python3 manage.py createsuperuser
Имя пользователя (leave blank to use 'tuser'): tuser
Адрес электронной почты: tmail138@mail.ru
Password:
Password (again):
root@aw:/# vim portfolio\admin.py
root@aw:/# cat portfolio\admin.py
from django.contrib import admin
from .models import Portfolio
admin.site.register(Portfolio)
root@aw:/# sudo apt install sqlite3
root@aw:/# sqlite3 db.sqlite3
sqlite> SELECT name from sqlite_master where type= "table";
django_migrations
sqlite_sequence
auth_group_permissions
auth_user_groups
auth_user_user_permissions
django_admin_log
django_content_type
auth_permission
auth_group
auth_user
django_session
portfolio_category
portfolio_portfolio
sqlite> INSERT INTO portfolio_category (name)
VALUES ("Категория1"), ("Категория2");
sqlite> select * from portfolio_category;
1|Категория1
2|Категория2
root@aw:/# python3 manage.py startapp portfolio
root@aw:/# cat settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portfolio.apps.PortfolioConfig',
'rest_framework',
]
...
root@aw:/# python3 manage.py runserver
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics
from .models import Portfolio
from .serializers import PortfolioSerializer
class portfolioAPIView(generics.ListAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
class PortfolioSerializer(serializers.ModelSerializer):
class Meta:
model = Portfolio
fields = ('title', 'cat_id')
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
from django.contrib import admin
from django.urls import path
from portfolio.views import PortfolioAPIView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
]
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/portfoliolist/ &
Task:
Базовый класс APIView для представлений.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics
from .models import Portfolio
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
#class portfolioAPIView(generics.ListAPIView):
# queryset = Portfolio.objects.all()
# serializer_class = PortfolioSerializer
class portfolioAPIView(APIView):
def get(self, request):
lst=portfolio.objects.all().values()
#return Response({'title':'Пост1'})
return Response({'posts':list(lst)})
def post(self, request):
#return Response({'title':'Пост2'})
post_new=portfolio.objects.create(
title=request.data['title'],
content=request.data['content'],
cat_id=request.data['cat_id']
)
return Response({'post':model_to_dict(post_new)})
root@aw:/# google-chrome https://web.postman.co/workspace/ &
Task:
Класс Serializer.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
from rest_framework.renderers import JSONRenderer
class portfolioModel:
def __init__(self, title, content):
self.title = title
self.content = content
#class PortfolioSerializer(serializers.ModelSerializer):
# class Meta:
# model = Portfolio
# fields = ('title', 'cat_id')
class PortfolioSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
def encode():
model = PortfolioModel('Post1', 'Content: Post1')
model_sr = PortfolioSerializer(model)
print(model_sr.data, type(model_sr.data), sep='\n')
json = JSONRenderer().render(model_sr.data)
print(json)
root@aw:/# python3 manage.py shell
>>> from portfolio.serializers import encode
>>> encode()
{'title': 'Post1', 'content': 'Content: Post1'}
<class 'rest_framework.utils.serializer_helpers.ReturnDict'>
b'{"title":"Post1","content":"Content: Post1"}'
>>> quit()
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
import io
...
def decode():
stream = io.BytesIO(b'{"title":"Post1", "content":"Content: Post1"}')
data = JSONParser().parse(stream)
serializer = PortfolioSerializer(data=data)
serializer.is_valid()
print(serializer.validated_data)
root@aw:/# python3 manage.py shell
>>> from portfolio.serializers import decode
>>> decode()
OrderedDict([('title', 'Post1'), ('content', 'Content: Post1')])
>>> quit()
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
from rest_framework import serializers
from .models import Portfolio
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
import io
#class portfolioModel:
# def __init__(self, title, content):
# self.title = title
# self.content = content
#class PortfolioSerializer(serializers.ModelSerializer):
# class Meta:
# model = Portfolio
# fields = ('title', 'cat_id')
class PortfolioSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
time_create=serializers.DateTimeField(read_only=True)
time_update=serializers.DateTimeField(read_only=True)
is_published=serializers.BooleanField(default=True)
cat_id=serializers.IntegerField()
#def encode():
# model = PortfolioModel('Post1', 'Content: Post1')
# model_sr = PortfolioSerializer(model)
# print(model_sr.data, type(model_sr.data), sep='\n')
# json = JSONRenderer().render(model_sr.data)
# print(json)
#def decode():
# stream = io.BytesIO(b'{"title":"Post1", "content":"Content: Post1"}')
# data = JSONParser().parse(stream)
# serializer = PortfolioSerializer(data=data)
# serializer.is_valid()
# print(serializer.validated_data)
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
class portfolioAPIView(APIView):
def get(self, request):
#lst=portfolio.objects.all().values()
#return Response({'title':'Пост1'})
#return Response({'posts':list(lst)})
w = portfolio.objects.all()
return Response({'posts':PortfolioSerializer(w, many=True).data})
def post(self, request):
#return Response({'title':'Пост2'})
serializer=PortfolioSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
post_new=portfolio.objects.create(
title=request.data['title'],
content=request.data['content'],
cat_id=request.data['cat_id']
)
#return Response({'post':model_to_dict(post_new)})
return Response({'post':PortfolioSerializer(post_new).data})
root@aw:/# python3 manage.py runserver
Task:
Методы save(), create() и update() класса Serializer.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
...
class PortfolioSerializer(serializers.Serializer):
title = serializers.CharField(max_length=255)
content = serializers.CharField()
time_create=serializers.DateTimeField(read_only=True)
time_update=serializers.DateTimeField(read_only=True)
is_published=serializers.BooleanField(default=True)
cat_id=serializers.IntegerField()
def create(self, validated_data):
return portfolio.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title=validated_data.get("title", instance.title)
instance.content=validated_data.get("content", instance.content)
instance.time_update=validated_data.get("time_update", instance.time_update)
instance.is_published=validated_data.get("is_published", instance.is_published)
instance.cat_id=validated_data.get("cat_id", instance.cat_id)
instance.save()
return instance
...
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
...
def post(self, request):
#return Response({'title':'Пост2'})
serializer=PortfolioSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
#post_new=portfolio.objects.create(
# title=request.data['title'],
# content=request.data['content'],
# cat_id=request.data['cat_id']
#)
#return Response({'post':model_to_dict(post_new)})
#return Response({'post':PortfolioSerializer(post_new).data})
serializer.save()
return Response({'post':serializer.data})
def put(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
if not pk:
return Response({"error": "Method PUT not allowed"})
try:
instance = portfolio.objects.get(pk=pk)
except:
return Response({"error": "Object does not exists"})
serializer = PortfolioSerializer(data=request.data, instance=instance)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"post": serializer.data})
def delete(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
if not pk:
return Response({"error": "Method DELETE not allowed"})
return Response({"post":"delete post "+str(pk)})
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
]
root@aw:/# python3 manage.py runserver
Task:
Класс ModelSerializer и представление ListCreateAPIView.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\serializers.py
root@aw:/# cat portfolio\serializers.py
...
#class PortfolioSerializer(serializers.Serializer):
#title = serializers.CharField(max_length=255)
#content = serializers.CharField()
#time_create=serializers.DateTimeField(read_only=True)
#time_update=serializers.DateTimeField(read_only=True)
#is_published=serializers.BooleanField(default=True)
#cat_id=serializers.IntegerField()
#def create(self, validated_data):
#return portfolio.objects.create(**validated_data)
#def update(self, instance, validated_data):
#instance.title=validated_data.get("title", instance.title)
#instance.content=validated_data.get("content", instance.content)
#instance.time_update=validated_data.get("time_update", instance.time_update)
#instance.is_published=validated_data.get("is_published", instance.is_published)
#instance.cat_id=validated_data.get("cat_id", instance.cat_id)
#instance.save()
#return instance
class PortfolioSerializer(serializers.ModelSerializer):
class Meta:
model=portfolio
#fields = ("title", "content", "cat")
fields="__all__"
...
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
#class portfolioAPIView(generics.ListAPIView):
class portfolioAPIList(generics.ListCreateAPIView):
queryset=portfolio.objects.all()
serializer_class=PortfolioSerializer
...
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
#from portfolio.views import PortfolioAPIView
from portfolio.views import *
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
]
root@aw:/# python3 manage.py runserver
Task:
Представления UpdateAPIView и RetrieveUpdateDestroyAPIView.
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
#class portfolioAPIView(APIView):
#def get(self, request):
#lst=portfolio.objects.all().values()
#return Response({'title':'Пост1'})
#return Response({'posts':list(lst)})
#w = portfolio.objects.all()
#return Response({'posts':PortfolioSerializer(w, many=True).data})
#def post(self, request):
#return Response({'title':'Пост2'})
#serializer=PortfolioSerializer(data=request.data)
#serializer.is_valid(raise_exception=True)
#post_new=portfolio.objects.create(
#title=request.data['title'],
#content=request.data['content'],
#cat_id=request.data['cat_id']
#)
#return Response({'post':model_to_dict(post_new)})
#return Response({'post':PortfolioSerializer(post_new).data})
#serializer.save()
#return Response({'post':serializer.data})
#def put(self, request, *args, **kwargs):
#pk = kwargs.get("pk", None)
#if not pk:
#return Response({"error": "Method PUT not allowed"})
#try:
#instance = portfolio.objects.get(pk=pk)
#except:
#return Response({"error": "Object does not exists"})
#serializer = PortfolioSerializer(data=request.data, instance=instance)
#serializer.is_valid(raise_exception=True)
#serializer.save()
#return Response({"post": serializer.data})
#def delete(self, request, *args, **kwargs):
#pk = kwargs.get("pk", None)
#if not pk:
#return Response({"error": "Method DELETE not allowed"})
#return Response({"post":"delete post "+str(pk)})
class portfolioAPIUpdate(generics.UpdateAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
class portfolioAPIDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
path('api/v1/portfoliolist/<int:pk>/', portfolioAPIUpdate.as_view()),
path('api/v1/portfoliodetail/<int:pk>/', portfolioAPIDetailView.as_view()),
]
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES':[
'rest_framework.renderers.JSONRenderer',
#'rest_framework.renderers.BrowsableAPIRenderer',
]
}
root@aw:/# python3 manage.py runserver
Task:
Viewsets и ModelViewSet
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics, viewsets, mixins
from .models import Portfolio
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
from rest_framework.viewsets import GenericViewSet
...
#class portfolioAPIList(generics.ListCreateAPIView):
#queryset=portfolio.objects.all()
#serializer_class=PortfolioSerializer
...
#class portfolioAPIUpdate(generics.UpdateAPIView):
#queryset = Portfolio.objects.all()
#serializer_class = PortfolioSerializer
#class portfolioAPIDetailView(generics.RetrieveUpdateDestroyAPIView):
#queryset = Portfolio.objects.all()
#serializer_class = PortfolioSerializer
#class portfolioViewSet(viewsets.ModelViewSet):
#class portfolioViewSet(viewsets.ReadOnlyModelViewSet):
class portfolioViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
from django.contrib import admin
from django.urls import path, include
#from portfolio.views import PortfolioAPIView
from portfolio.views import *
from rest_framework import routers
router=routers.SimpleRouter()
router.register(r'portfolio', portfolioViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
#path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIUpdate.as_view()),
#path('api/v1/portfoliodetail/<int:pk>/', portfolioAPIDetailView.as_view()),
#path('api/v1/portfoliolist/', portfolioViewSet.as_view({'get':'list'})),
#path('api/v1/portfoliodetail/<int:pk>/', portfolioViewSet.as_view({'put':'update'})),
path('api/v1/', include(router.urls)),
]
root@aw:/# python3 manage.py runserver
Task:
Роутеры: SimpleRouter и DefaultRouter
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
class MyCustomRouter(routers.SimpleRouter):
routes = [
routers.Route(url=r'^{prefix}$',
mapping={'get': 'list'},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}),
routers.Route(url=r'^{prefix}/{lookup}$',
mapping={'get': 'retrieve'},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Detail'})
]
#router=routers.SimpleRouter()
#router=routers.DefaultRouter()
router=MyCustomRouter()
#router.register(r'portfolio', portfolioViewSet)
#router.register(r'portfolio', portfolioViewSet, basename='men')
router.register(r'portfolio', portfolioViewSet, basename='portfolio')
print(router.urls)
...
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
from django.shortcuts import render
from rest_framework import generics, viewsets, mixins
from .models import Portfolio, Category
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
...
class portfolioViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
#queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
def get_queryset(self):
#return portfolio.objects.all()[:3]
pk=self.kwargs.get("pk")
if not pk:
return portfolio.objects.all()[:3]
return portfolio.objects.filter(pk=pk)
@action(methods=['get'], detail=True)
def category(self, request, pk=None):
#cats = Category.objects.all()
#return Response({'cats': [c.name for c in cats]})
cats = Category.objects.get(pk=pk)
return Response({'cats': cats.name})
root@aw:/# python3 manage.py runserver
Task:
Ограничения доступа (permissions)
# Разработка сайта на Django.
Decision:
root@aw:/# vim portfolio\models.py
root@aw:/# cat portfolio\models.py
from django.db import models
from django.contrib.auth.models import User
...
cat=models.ForeignKey('Category', on_delete=models.PROTECT, null=True, verbose_name="Категории")
user=models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Пользователь")
def __str__(self):
...
root@aw:/# python3 manage.py makemigrations
Select an option: 1
>>> 1
root@aw:/# python3 manage.py migrate
root@aw:/# vim portfolio/views.py
root@aw:/# cat portfolio/views.py
from django.shortcuts import render
from rest_framework import generics, viewsets, mixins
from .models import Portfolio, Category
from .serializers import PortfolioSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from django.forms import model_to_dict
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser
from .permissions import IsAdminOrReadOnly, IsOwnerOrReadOnly
class portfolioAPIList(generics.ListCreateAPIView):
queryset=portfolio.objects.all()
serializer_class=PortfolioSerializer
#permissions_classes=(IsAuthenticatedOrReadOnly, )
class portfolioAPIUpdate(generics.RetrieveUpdateAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
permissions_classes=(IsOwnerOrReadOnly, )
class portfolioAPIDestroy(generics.RetrieveDestroyAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
permissions_classes=(IsAdminOrReadOnly, )
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
#class MyCustomRouter(routers.SimpleRouter):
# routes = [
# routers.Route(url=r'^{prefix}$',
# mapping={'get': 'list'},
# name='{basename}-list',
# detail=False,
# initkwargs={'suffix': 'List'}),
# routers.Route(url=r'^{prefix}/{lookup}$',
# mapping={'get': 'retrieve'},
# name='{basename}-detail',
# detail=True,
# initkwargs={'suffix': 'Detail'})
# ]
#router=routers.SimpleRouter()
#router=routers.DefaultRouter()
#router=MyCustomRouter()
#router.register(r'portfolio', portfolioViewSet)
#router.register(r'portfolio', portfolioViewSet, basename='men')
#router.register(r'portfolio', portfolioViewSet, basename='portfolio')
#print(router.urls)
urlpatterns = [
path('admin/', admin.site.urls),
#path('api/v1/portfoliolist/', portfolioAPIView.as_view()),
#path('api/v1/portfoliolist/', portfolioAPIList.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIView.as_view()),
#path('api/v1/portfoliolist/<int:pk>/', portfolioAPIUpdate.as_view()),
#path('api/v1/portfoliodetail/<int:pk>/', portfolioAPIDetailView.as_view()),
#path('api/v1/portfoliolist/', portfolioViewSet.as_view({'get':'list'})),
#path('api/v1/portfoliodetail/<int:pk>/', portfolioViewSet.as_view({'put':'update'})),
#path('api/v1/', include(router.urls)),
path('api/v1/portfolio/', portfolioAPIList.as_view()),
path('api/v1/portfolio/<int:pk>/', portfolioAPIUpdate.as_view()),
path('api/v1/portfoliodelete/<int:pk>/', portfolioAPIDestroy.as_view()),
]
root@aw:/# vim portfolio/serializers.py
root@aw:/# cat portfolio/serializers.py
...
class PortfolioSerializer(serializers.ModelSerializer):
user=serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model=portfolio
#fields = ("title", "content", "cat")
fields="__all__"
...
root@aw:/# touch portfolio/permissions.py
root@aw:/# vim portfolio/permissions.py
root@aw:/# cat portfolio/permissions.py
from rest_framework import permissions
class IsAdminOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return bool(request.user and request.user.is_staff)
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES':[
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PERMISSION_CLASSES':[
#'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.AllowAny',
]
}
root@aw:/# python3 manage.py runserver
Task:
Авторизация и аутентификация. Session-based authentication.
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
[
...
path('api/v1/portfoliodelete/<int:pk>/', portfolioAPIDestroy.as_view()),
path('api/v1/drf-auth/', include('rest_framework.urls')),
]
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/drf-auth/
Task:
Аутентификация по токенам. Пакет Djoser
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portfolio.apps.PortfolioConfig',
'rest_framework',
'rest_framework.authtoken',
'djoser',
]
...
root@aw:/# python3 manage.py migrate
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
...
from django.urls import path, include, re_path
[
...
path('api/v1/drf-auth/', include('rest_framework.urls')),
path('api/v1/auth/', include('djoser.urls')),
re_path(r'^auth/', include('djoser.urls.authtoken')),
]
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES':[
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser, IsAuthenticated
from .permissions import IsAdminOrReadOnly, IsOwnerOrReadOnly
from rest_framework.authentication import TokenAuthentication
...
class portfolioAPIUpdate(generics.RetrieveUpdateAPIView):
queryset = Portfolio.objects.all()
serializer_class = PortfolioSerializer
#permissions_classes=(IsOwnerOrReadOnly, )
permissions_classes=(IsAuthenticated, )
#authentication_classes = (TokenAuthentication, )
...
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/auth/ &
Task:
Делаем авторизацию по JWT-токенам
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
from pathlib import Path
from datetime import timedelta
...
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES':[
#'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
"ROTATE_REFRESH_TOKENS": False,
"BLACKLIST_AFTER_ROTATION": False,
"UPDATE_LAST_LOGIN": False,
"ALGORITHM": "HS256",
"SIGNING_KEY": SECRET_KEY,
"VERIFYING_KEY": "",
"AUDIENCE": None,
"ISSUER": None,
"JSON_ENCODER": None,
"JWK_URL": None,
"LEEWAY": 0,
#"AUTH_HEADER_TYPES": ("Bearer",),
"AUTH_HEADER_TYPES": ("JWT",),
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
"USER_ID_FIELD": "id",
"USER_ID_CLAIM": "user_id",
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
"TOKEN_TYPE_CLAIM": "token_type",
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
"JTI_CLAIM": "jti",
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
}
root@aw:/# vim dato138it\urls.py
root@aw:/# cat dato138it\urls.py
from django.contrib import admin
from django.urls import path, include, re_path
#from portfolio.views import PortfolioAPIView
from portfolio.views import *
from rest_framework import routers
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
...
urlpatterns = [
...
re_path(r'^auth/', include('djoser.urls.authtoken')),
path('api/v1/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path('api/v1/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/v1/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/token/ &
Task:
Добавляем пагинацию (pagination)
# Разработка сайта на Django.
Decision:
root@aw:/# vim dato138it\settings.py
root@aw:/# cat dato138it\settings.py
...
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 2,
...
}
root@aw:/# vim portfolio\views.py
root@aw:/# cat portfolio\views.py
...
from rest_framework.pagination import PageNumberPagination
#class portfolioAPIView(generics.ListAPIView):
class portfolioAPIListPagination(PageNumberPagination):
page_size=3
page_size_query_param='page_size'
max_page_size=10000
class portfolioAPIList(generics.ListCreateAPIView):
queryset=portfolio.objects.all()
serializer_class=PortfolioSerializer
permissions_classes=(IsAuthenticatedOrReadOnly, )
pagination_class=portfolioAPIListPagination
...
root@aw:/# python3 manage.py runserver
root@aw:/# google-chrome http://127.0.0.1:8000/api/v1/portfolio/ &
Source:
# https://www.youtube.com/playlist?list=PLA0M1Bcd0w8xZA3Kl1fYmOH_MfLpiYMRs
# https://www.freecodecamp.org/news/python-requirementstxt-explained/
# https://timeweb.cloud/tutorials/sqlite/rukovodstvo-po-nastrojke-sqlite
# https://itfy.org/threads/kak-poluchit-spisok-tablic-v-sqlite3.695/
# https://linuxgenie.net/how-to-install-postman-on-ubuntu-22-04/
# https://pyonlycode.com/post/how-to-solve-nameerror-name-io-is-not-defined/
# https://django-rest-framework-simplejwt.readthedocs.io/en/latest/index.html
# https://jwt.io/
Task:
B проекте нужно сделать так, чтобы необязательно заполнялось определенное поле. В нашем случае это поле doc.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# cat portfolio/models.py
...
doc=models.FileField(upload_to="uploads/%Y/%m/%d/", verbose_name="Файлы")
root@kvmubuntu:~# vim portfolio/models.py
root@kvmubuntu:~# cat portfolio/models.py
...
doc=models.FileField(upload_to="uploads/%Y/%m/%d/", verbose_name="Файлы", blank=True, null=True)
root@kvmubuntu:~# python3 manage.py makemigrations
root@kvmubuntu:~# python3 manage.py migrate
root@kvmubuntu:~# python3 manage.py runserver
Source:
# https://ru.stackoverflow.com/questions/1059864/%D0%A1%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C-%D0%BD%D0%B5%D0%BE%D0%B1%D1%8F%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%BC-%D0%BF%D0%BE%D0%BB%D0%B5-arrayfield-%D0%B2-django-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8 - Сделать необязательным поле ArrayField в Django модели.
# https://ru.stackoverflow.com/questions/758588/%D0%9A%D0%B0%D0%BA-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D0%BD%D0%BE%D0%B2%D0%BE%D0%B5-%D0%BF%D0%BE%D0%BB%D0%B5-%D0%B2-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C-django - Как добавить новое поле в модель django.
Task:
Integrating CKEditor in Django Admin and Rendering HTML in a template.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# pip install django-ckeditor==6.7.1
django-ckeditor-5==0.2.12
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
INSTALLED_APPS = [
...,
'ckeditor',
'ckeditor_uploader',
]
...
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media/"
#ckeditor upload path
CKEDITOR_UPLOAD_PATH="uploads/"
CKEDITOR_CONFIGS = {
'default': {
# 'skin': 'moono',
# # 'skin': 'office2013',
# 'toolbar_Basic': [
# ['Source', '-', 'Bold', 'Italic']
# ],
'toolbar_Custom': [
{'name': 'document', 'items': ['Source', '-', 'Save', 'NewPage', 'Preview', 'Print', '-', 'Templates']},
{'name': 'clipboard', 'items': ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo']},
{'name': 'editing', 'items': ['Find', 'Replace', '-', 'SelectAll']},
{'name': 'forms',
'items': ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton',
'HiddenField']},
'/',
{'name': 'basicstyles',
'items': ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat']},
{'name': 'paragraph',
'items': ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-',
'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl',
'Language']},
{'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']},
{'name': 'insert',
'items': ['Image', 'Youtube','Flash', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', 'Iframe']},
'/',
{'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']},
{'name': 'colors', 'items': ['TextColor', 'BGColor']},
{'name': 'tools', 'items': ['Maximize', 'ShowBlocks']},
{'name': 'about', 'items': ['CodeSnippet']},
{'name': 'about', 'items': ['About']},
'/', # put this to force next toolbar on new line
{'name': 'yourcustomtools', 'items': [
# put the name of your editor.ui.addButton here
'Preview',
'Maximize',
]},
],
'toolbar': 'Custom', # put selected toolbar config here
'toolbarGroups': [{ 'name': 'document', 'groups': [ 'mode', 'document', 'doctools' ] }],
'height': 400,
# 'width': '100%',
'filebrowserWindowHeight': 725,
'filebrowserWindowWidth': 940,
'toolbarCanCollapse': True,
'mathJaxLib': '//cdn.mathjax.org/mathjax/2.2-latest/MathJax.js?config=TeX-AMS_HTML',
'tabSpaces': 4,
'extraPlugins': ','.join([
'uploadimage', # the upload image feature
# your extra plugins here
'div',
'autolink',
'autoembed',
'embedsemantic',
'autogrow',
'devtools',
'widget',
'lineutils',
'clipboard',
'dialog',
'dialogui',
'elementspath',
'codesnippet',
]),
}
}
root@kvmubuntu:~# vim dato138it/urls.py
root@kvmubuntu:~# cat dato138it/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
...
path('ckeditor/',include('ckeditor_uploader.urls')),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
root@kvmubuntu:~# vim portfolio/models.py
root@kvmubuntu:~# cat portfolio/models.py
from ckeditor_uploader.fields import RichTextUploadingField
...
class Portfolio(models.Model):
title = models.TextField(verbose_name="Достижение")
description = RichTextUploadingField(verbose_name="Статья")
image = models.ImageField(upload_to='uploads/%Y/%m/%d/', blank=True, null=True)
url = models.URLField(blank=True, null=True)
ordinal = models.IntegerField()
...
root@kvmubuntu:~# python manage.py makemigrations
root@kvmubuntu:~# python manage.py migrate
root@kvmubuntu:~# python manage.py runserver
Source:
# https://www.codesnail.com/integrating-ckeditor-in-django-admin-and-rendering-html-in-a-template-django-blog-4/ - Integrating CKEditor in Django Admin and Rendering HTML in a template.
Task:
Как добавить страницу в Django.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# vim dato138it/urls.py
root@kvmubuntu:~# cat dato138it/urls.py
...
#from django.http import HttpResponse
#def show_contents(request):
# print('Кто-то зашёл на главную!')
# return HttpResponse('Привет!')
from portfolio import views
urlpatterns = [
...
path('contents/', views.show_contents),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
root@kvmubuntu:~# vim portfolio/views.py
root@kvmubuntu:~# cat portfolio/views.py
...
from django.http import HttpResponse
...
def show_contents(request):
print('Кто-то зашёл на главную!')
return HttpResponse('Привет!')
root@kvmubuntu:~# python manage.py runserver tipubuntu:8000
root@kvmubuntu:~# google-chrome http://tipubuntu:8000/contents/
root@kvmubuntu:~# cp -r /var/www/tdb ../backend
root@kvmubuntu:~# ls tdb/
css index.html js
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'tdb')],
...
},
]
...
root@kvmubuntu:~# vim portfolio/views.py
root@kvmubuntu:~# cat portfolio/views.py
...
from django.http import HttpResponse
from django.template import loader
...
def show_contents(request):
#print('Кто-то зашёл на главную!')
template = loader.get_template('index.html')
context = {}
rendered_page = template.render(context, request)
return HttpResponse(rendered_page)
Source:
# https://dvmn.org/encyclopedia/django/how-to-add-page/?ysclid=lul44qesp8431289715 - Как добавить страницу в Django.
# https://codeease.net/programming/python/NameError-name-os-is-not-defined-django#:~:text=NameError%3A%20name%20'os'%20is%20not,directory%20operations%2C%20and%20environment%20variables - NameError name os is not defined django.
Task:
Настройка и подключение статических файлов в Django.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# mkdir static
root@kvmubuntu:~# vim dato138it/settings.py
root@kvmubuntu:~# cat dato138it/settings.py
...
import os
...
INSTALLED_APPS = [
...
'django.contrib.staticfiles',
...
]
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'tdb')],
'APP_DIRS': True,
...
...
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
...
root@kvmubuntu:~# mv tdb/css static/
root@kvmubuntu:~# mv tdb/js static/
root@kvmubuntu:~# ls static/
css js
root@kvmubuntu:~# vim tdb/index.html
root@kvmubuntu:~# cat tdb/index.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<!--SECTIONS CARDS-->
<section>
<div class="container">
{% if content.count > 0 %}
<div class="cards">
{% for portfolio in content %}
<div class="card__content" id="" style="text-align: left;">
<p class="card_hidden" onclick="card__hidden(this)">{{portfolio.progress}}</p>
<p>{{portfolio.description}}</p>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</section>
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
tuser@kvmubuntu:~$ python manage.py runserver tipubuntu:8000
Source:
# https://pythonru.com/uroki/django-static?ysclid=lul6hmdpge797629443 - Настройка и подключение статических файлов в Django.
Task:
Настройка блоков с разделами.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# vim portfolio/models.py
root@kvmubuntu:~# cat portfolio/models.py
from django.db import models
...
class Place(models.Model):
...
class Portfolio(models.Model):
progress = models.TextField(verbose_name="Достижение")
description = RichTextUploadingField(verbose_name="Описание")
image = models.ImageField(upload_to='uploads/%Y/%m/%d/', blank=True, null=True)
url = models.URLField(blank=True, null=True)
class Category(models.Model):
...
root@kvmubuntu:~# python manage.py makemigrations
...
Was portfolio.title renamed to portfolio.progress (a TextField)? [y/N] y
...
root@kvmubuntu:~# python manage.py migrate
root@kvmubuntu:~# vim portfolio/views.py
root@kvmubuntu:~# cat portfolio/views.py
...
from .models import Place, Portfolio, Category
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.shortcuts import render
class UserViewSet(viewsets.ModelViewSet):
...
class PlaceViewSet(viewsets.ModelViewSet):
...
class PortfolioViewSet(viewsets.ModelViewSet):
...
class CategoryViewSet(viewsets.ModelViewSet):
...
def show_contents(request):
...
# получение данных из бд
def index(request):
content = Portfolio.objects.all()
return render(request, "index.html", {"content": content})
root@kvmubuntu:~# vim tdb/index.html
root@kvmubuntu:~# cat tdb/index.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<title>dato138it</title>
</head>
<body>
<!--SECTIONS CARDS-->
<section>
<div class="container">
{% if content.count > 0 %}
<div class="cards">
{% for portfolio in content %}
<div class="card__content" id="" style="text-align: left;">
{{portfolio.progress|safe}}
<p class="card_hidden" onclick="card__hidden(this)">Show</p>
<div style="display:none;" style=&{head};>{{portfolio.description|safe}}</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</section>
<!-- JS -->
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
root@kvmubuntu:~# vim dato138it/urls.py
root@kvmubuntu:~# cat dato138it/urls.py
...
from django.urls import path, include
from portfolio import views
urlpatterns = [
...
path("index/", views.index),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Source:
# https://metanit.com/python/django/5.4.php - CRUD.
# https://stackoverflow.com/questions/17880663/ckeditor-shows-me-html-code - CKEditor shows me html code.
Task:
Сделать страницу контентов основной страницей сайта.
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# vim /var/www/dato138it/dato138it/urls.py
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/urls.py
....
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
#path('', include('portfolio.urls')),
path("ckeditor5/", include('django_ckeditor_5.urls'), name="ck_editor_5_upload_file"),
#path('contents/', views.contents),
path('urls-rest/', include('portfolio.urls')),
path('', views.contents),
...
Task:
Как скрыть токены и пароли в python?
# Разработка сайта на Django.
Decision:
root@kvmubuntu:~# source /var/www/dato138it/djangoenv/bin/activate
root@kvmubuntu:~# vim requirements.txt
root@kvmubuntu:~# cat requirements.txt
...
python-decouple==3.8
python-dotenv==1.0.1
root@kvmubuntu:~# pip install -r requirements.txt
root@kvmubuntu:~# vim /var/www/dato138it/.env
root@kvmubuntu:~# cat /var/www/dato138it/.env
DB_NAME = 'tbase'
DB_USER = 'tuser'
DB_PASSWD = 'tpassword'
SERVER_IP = 'tipubuntu'
DB_PORT = '5432'
DJANGO_KEY = 'tkey'
root@kvmubuntu:~# vim /var/www/dato138it/dato138it/settings.py
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/settings.py
from pathlib import Path
import os
#from dotenv import load_dotenv
from decouple import config
...
SECRET_KEY = config('DJANGO_KEY')
...
ALLOWED_HOSTS = [config('SERVER_IP'), 'dato138it.ru']
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWD'),
'HOST': config('SERVER_IP'),
'PORT': config('DB_PORT'),
}
}
...
root@kvmubuntu:~# ls -la /var/www/dato138it/.env
-rw-r--r--. 1 tuser tuser 191 Aug 1 16:06 /var/www/dato138it/.env
root@kvmubuntu:~# chown root:root /var/www/dato138it/.env
root@kvmubuntu:~# chmod 604 /var/www/dato138it/.env
root@kvmubuntu:~# ls -la /var/www/dato138it/.env
-rw----r--. 1 root root 191 Aug 1 16:06 /var/www/dato138it/.env
Source:
# https://www.youtube.com/watch?v=OQ6cEG0ykVs&list=PLV0FNhq3XMOJ31X9eBWLIZJ4OVjBwb-KM&index=10 - PostgreSQL + Скрытие Токена в .env - Aiogram 3.
# https://dev.to/earthcomfy/django-how-to-keep-secrets-safe-with-python-dotenv-5811 - Django - How to keep secrets safe with python-dotenv.
# https://dontrepeatyourself.org/post/how-to-use-python-decouple-with-django/ - How to Use Python Decouple with Django.
# https://redos.red-soft.ru/base/arm/share/network-directories-connection-with-automount/ - Автоматическое монтирование ресурсов CIFS.
Task:
как привязать домен timeweb к начальной странице проекта веб-сервера django если уже установлен Apache?
# Администрирование веб-сервера Django.
Decision:
root@kvmubuntu:~# apt install libapache2-mod-wsgi-py3
root@kvmubuntu:~# systemctl restart apache2
root@kvmubuntu:~# systemctl status apache2
root@kvmubuntu:~# cp -r /home/tuser/dato138it/* /var/www/dato138it/
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/settings.py
...
from pathlib import Path
import os
...
ALLOWED_HOSTS = ['tipubuntu', 'dato138it.ru']
...
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
STATIC_ROOT=os.path.join(BASE_DIR, 'static/')
...
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media/"
#MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
...
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf
<VirtualHost *:80>
ServerName dato138it
ServerAlias www.dato138it
ServerAdmin webmaster@localhost
DocumentRoot /var/www/dato138it
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
root@kvmubuntu:~# vim /etc/apache2/sites-available/dato138it.conf
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf
<VirtualHost *:8033>
ServerAdmin tmail@mail.ru
ServerName dato138it.ru
ServerAlias www.dato138it.ru
DocumentRoot /var/www/dato138it
ErrorLog ${APACHE_LOG_DIR}/dato138it.ru_error.log
CustomLog ${APACHE_LOG_DIR}/dato138it.ru_access.log combined
Alias /static /var/www/dato138it/static
<Directory /var/www/dato138it/static>
Require all granted
</Directory>
Alias /media /var/www/dato138it/media
<Directory /var/www/dato138it/media>
Require all granted
</Directory>
<Directory /var/www/dato138it/dato138it>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
WSGIDaemonProcess dato138it python-path=/var/www/dato138it python-home=/var/www/dato138it/djangoenv
WSGIProcessGroup dato138it
WSGIScriptAlias / /var/www/dato138it/dato138it/wsgi.py
</VirtualHost>
root@kvmubuntu:~# cat /etc/apache2/sites-available/dato138it.conf | grep djangoenv
WSGIDaemonProcess dato138it python-path=/var/www/dato138it python-home=/var/www/dato138it/djangoenv
root@kvmubuntu:~# a2ensite dato138it.conf
root@kvmubuntu:~# apache2ctl configtest
root@kvmubuntu:~# systemctl restart apache2
root@kvmubuntu:~# systemctl reload apache2
root@kvmubuntu:~# google-chrome http://tipubuntu:80
root@kvmubuntu:~# vim /var/www/dato138it/dato138it/settings.py
root@kvmubuntu:~# cat /var/www/dato138it/dato138it/settings.py
...
STATIC_URL = '/static/'
#STATICFILES_DIRS = [
# os.path.join(BASE_DIR, "static"),
#]
STATIC_ROOT=os.path.join(BASE_DIR, 'static/')
...
root@kvmubuntu:~# python /var/www/dato138it/manage.py collectstatic
root@kvmubuntu:~# google-chrome http://tipubuntu:80
Source:
# https://www.linuxtuto.com/how-to-install-django-with-apache-on-ubuntu-22-04/ - How to Install Django with Apache on Ubuntu 22.04.
2024-06-02 - None: Tele2, Иркутск. Должность: Инженер эксплуатации подсистемы базовых станций. Дополнительная информация: Обязанности - Обеспечивать эксплуатацию оборудования контроллеров и базовых станций стандартов 2G/3G/4G, Обеспечивать локальную поддержку работ по аварийному восстановлению работоспособности оборудования контроллеров, Поддерживать ввод в работу новых узлов контроллеров базовых станций, Предоставлять техническую поддержку региональным инженерам по эксплуатации базовых станций и транспортной сети, инженерам по эксплуатации коммутаторов по вопросам работы подсистемы базовых станций. Достижения: Разработал скрипт , который делает бэкап файлов конфигураций базовых станций.
Show
# Написание скриптов для бэкап файлов.
# Администрирование локальных, виртуальных и облачных серверов.
Task:
Написание скриптов для бэкап файлов. Написать скрипт, который копирует файлы с ftp://tipftp/Backups/tdir/MRBTS* в сетевую папку \\tdomain.ru\tdir каждую неделю ночью.
Decision:
PS C:\Users\tuser\Documents\scripts> vim mrbtsbackup.bat
PS C:\Users\tuser\Documents\scripts> cat mrbtsbackup.bat
rem xcopy C:\Users\tuser\Documents\scripts\new1\*.txt C:\Users\tuser\Documents\scripts\new2 /e
xcopy C:\Users\tuser\Documents\scripts\new1\test2.txt C:\Users\tuser\Documents\scripts\new2 /s
PS C:\Users\tuser\Documents\scripts> ls .\new1\
Каталог: C:\Users\tuser\Documents\scripts\new1
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 05.09.2024 14:21 test
d----- 05.09.2024 14:18 test2
PS C:\Users\tuser\Documents\scripts> ls .\new1\test\
Каталог: C:\Users\tuser\Documents\scripts\new1\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 05.09.2024 14:17 Новая папка
d----- 05.09.2024 14:18 Новая папка (2)
-a---- 05.09.2024 14:00 3 test.txt
-a---- 05.09.2024 14:00 3 test2.txt
Task:
Администрирование локальных, виртуальных и облачных серверов. Подключиться к ftp и скопировать файлы в сетевую папку.
Decision:
PS C:\Users\tuser\Documents\scripts> ftp
ftp> open tipftp
ftp> tuser
ftp> ls Backups/tdir
ftp> get /Backups/tdir/MRBTS2279.xml \\tdomain.ru\tdir\Test.txt
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for /Backups/tdir/MRBTS2279.xml (126410 bytes).
226 Transfer complete.
ftp: 126410 байт получено за 0.24 (сек) со скоростью 533.38 (КБ/сек).
Task:
Написание скриптов для бэкап файлов. Реализовать подключение и копирование файлов скриптом
Decision:
PS C:\Users\tuser\Documents\scripts> vim mrbtsbackup.bat
PS C:\Users\tuser\Documents\scripts> cat mrbtsbackup.bat
@Echo Off
:: ~Параметры соединения
Set $Host=tipftp
Set $User=tuser
Set $Pass=tpassword
:: ~Что и куда копируем
SET $SRC=/Backups/tdir/MRBTS2279.xml
SET $DST=\\tdomain.ru\tdir\Test2.txt
:: ~Временные файлы
Set $FFtp=%~dpn0.cfg
:: Готовим CFG-файл
Echo.%$User%>"%$FFtp%"
Echo.%$Pass%>>"%$FFtp%"
Echo get "%$SRC%" "%$DST%">>"%$FFtp%"
Echo bye>>"%$FFtp%"
:: Выполняем команду
FTP -s:"%$FFtp%" %$Host%
rem exit
pause
Task:
Написание скриптов для бэкап файлов. Написать команды, которые заархивируют скопированные файлы и удалят файлы. Добавить в скрипт команды
Decision:
C:\WINDOWS\system32> "C:\Program Files\7-Zip\7z.exe" a -tzip \\tdomain.ru\tdir\Test.zip \\tdomain.ru\tdir\*.txt
C:\WINDOWS\system32> del \\tdomain.ru\tdir\*.txt
PS C:\Users\tuser\Documents\scripts> vim mrbtsbackup.bat
PS C:\Users\tuser\Documents\scripts> cat mrbtsbackup.bat
@Echo Off
:: ~Параметры соединения
Set $Host=tipftp
Set $User=tuser
Set $Pass=tpassword
:: ~Что и куда копируем
SET $SRC=/Backups/tdir/MRBTS2279.xml
SET $DST=\\tdomain.ru\tdir\Test2.txt
:: Формат текущей даты
SET dd=%date:~0,2%
SET mm=%date:~3,2%
SET yyyy=%date:~6,4%
SET curdate=%dd%_%mm%_%yyyy%
:: ~Временные файлы
Set $FFtp=%~dpn0.cfg
:: Готовим CFG-файл
Echo.%$User%>"%$FFtp%"
Echo.%$Pass%>>"%$FFtp%"
Echo get "%$SRC%" "%$DST%">>"%$FFtp%"
Echo bye>>"%$FFtp%"
:: Выполняем команду
FTP -s:"%$FFtp%" %$Host%
:: Добавим в архив скопированные файлы
"C:\Program Files\7-Zip\7z.exe" a -tzip \\tdomain.ru\tdir\Test_%curdate%.zip \\tdomain.ru\tdir\*.txt
:: Удалим лишние файлы
del \\tdomain.ru\tdir\*.txt
rem exit
pause
Task:
Написание скриптов для бэкап файлов. Обработать все файлы с расширением .xml.
Decision:
PS C:\Users\tuser\Documents\scripts> vim mrbtsbackup.bat
PS C:\Users\tuser\Documents\scripts> cat mrbtsbackup.bat
@Echo Off
:: ~Параметры соединения
Set server=tipftp
Set user=tuser
Set pass=tpassword
:: ~Что и куда копируем
:: SET $SRC=/Backups/tdir/*.xml
SET src=/Backups/tdir
SET dst=\\tdomain.ru\tdir\
:: Формат текущей даты
SET dd=%date:~0,2%
SET mm=%date:~3,2%
SET yyyy=%date:~6,4%
SET curdate=%dd%_%mm%_%yyyy%
:: ~Временные файлы
::Set $FFtp=%~dpn0.cfg
:: Готовим CFG-файл
Echo open %server%>tempfile.txt
Echo %user%>>tempfile.txt
Echo %pass%>>tempfile.txt
Echo lcd %dst%>>tempfile.txt
Echo cd %src%>>tempfile.txt
:: Echo mget *.* | Y>>tempfile.txt
Echo bye>>tempfile.txt
:: Выполняем команду
FTP -s:tempfile.txt
:: Добавим в архив скопированные файлы
"C:\Program Files\7-Zip\7z.exe" a -tzip \\tdomain.ru\tdir\Test_%curdate%.zip \\tdomain.ru\tdir\*.xml
:: Удалим лишние файлы
del \\tdomain.ru\tdir\*.txt
rem exit
pause
Task:
Написание скриптов для бэкап файлов. Реализация бэкап файлов на языке PowerShell.
Decision:
PS C:\Users\tuser\Documents\scripts> Get-Date -Format "_MM_dd_yyyy_HH_mm"
_09_09_2024_09_23
PS C:\Users\tuser\Documents\scripts> vim mrbtsbackup.ps1
PS C:\Users\tuser\Documents\scripts> cat mrbtsbackup.ps1
# Переменные
$ftpServer = "ftp://tipftp/Backups/tdir/"
$ftpUser = "tuser"
$ftpPassword = "tpassword"
$localFolder = "\\tdomain.ru\tdir\"
$curdate = Get-Date -Format "_MM_dd_yyyy_HH_mm"
#Write-Output $curdate
# Создание объекта для загрузки
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
# Получение списка файлов на FTP-сервере
$ftpRequest = [System.Net.FtpWebRequest]::Create($ftpServer)
$ftpRequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory
$ftpRequest.Credentials = $webclient.Credentials
$response = $ftpRequest.GetResponse()
$streamReader = New-Object System.IO.StreamReader($response.GetResponseStream())
# Чтение имен файлов
$files = @()
while($file = $streamReader.ReadLine()) {
$files += $file
}
$streamReader.Close()
$response.Close()
#Write-Output $files
# Скачивание файлов
foreach ($file in $files) {
$remoteFile = "$ftpServer/$file"
$localFile = Join-Path $localFolder $file
$webclient.DownloadFile($remoteFile, $localFile)
}
# Завершение работы
$webclient.Dispose()
$compress = @{
Path = "\\tdomain.ru\tdir\*.xml"
CompressionLevel = "Fastest"
DestinationPath = "\\tdomain.ru\tdir\IRK$curdate.zip"
}
Compress-Archive @compress
Remove-Item \\tdomain.ru\tdir\*.xml
PS C:\Users\tuser\Documents\scripts\backups> powershell -file mrbtsbackup.ps1
PS C:\Users\tuser\Documents\scripts\backups> ls \\tdomain.ru\tdir\
Task:
Администрирование локальных, виртуальных и облачных серверов. Добавить раписание.
Decision:
- Планировщик задач - Создать задачу - Имя - Backup MTBTS - +Выполнить для всех пользователей - Триггеры - +Еженедельно - Начать - суббота - 23:00 - ок - действия - запуск программы - Программа или сценарий - powershell.exe - добавить аргументы
-file "C:\Users\tuser\Documents\scripts\backups\mrbtsbackup.ps1"
- ок
Source:
# https://comp-security.net/%D0%BA%D0%B0%D0%BA-%D1%81%D0%BA%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C-%D1%84%D0%B0%D0%B9%D0%BB-%D0%BF%D0%B0%D0%BF%D0%BA%D1%83-%D0%B2-cmd/ - Как скопировать файл (папку) в командной строке Windows.
# https://pc.ru/articles/kak-dobavit-kommentarii-v-bat-fajl - Как добавить комментарии в bat-файл.
# https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.utility/get-date?view=powershell-7.4 - Get-Date.
# https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.management/remove-item?view=powershell-7.4 - Remove-Item.
# https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.utility/write-output?view=powershell-7.4 - Write-Output.
# https://docs.oracle.com/cd/E19120-01/open.solaris/819-1634/remotehowtoaccess-87541/index.html - How to Copy Files From a Remote System (ftp).
# http://forum.oszone.net/post-2962057.html - Powershell. Копировать файлы в новую dir, созданную с именем текущей даты.
# https://windowsnotes.ru/powershell-2/zapusk-powershell-skripta-po-raspisaniyu/ - Способ 1.
# https://stackoverflow.com/questions/18180060/how-to-zip-a-file-using-cmd-line - How to zip a file using cmd line?
# https://learn.microsoft.com/ru-ru/windows-server/administration/windows-commands/del - del.
# https://www.dmosk.ru/miniinstruktions.php?mini=7zip-cmd - Резервное копирование с помощью 7-Zip.
Task:
Написание скриптов для бэкап файлов. Знакомство с Python.
Decision:
[tuser@kvmredhat(enm11) scripts]$ hostnamectl
Static hostname: kvmredhat
Icon name: computer-vm
Chassis: vm
Machine ID: tid1
Boot ID: tid2
Virtualization: kvm
Operating System: Red Hat Enterprise Linux Server 7.6 (Maipo)
CPE OS Name: cpe:/o:redhat:enterprise_linux:7.6:GA:server
Kernel: Linux 3.10.0-957.66.1.el7.x86_64
Architecture: x86-64
[tuser@kvmredhat(enm11) scripts]$ python3 -V
-bash: python3: command not found
[tuser@kvmredhat(enm11) scripts]$ python -V
Python 2.7.5
PS P:\> cd c:\Users\tuser\Documents\py\
PS C:\Users\tuser\Documents\py> python -m venv tenv
PS C:\Users\tuser\Documents\py> ls
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 09.09.2024 16:20 tenv
-a---- 09.09.2024 15:59 264 tpy.py
PS C:\Users\tuser\Documents\py> .\tenv\Scripts\activate
pip install
(tenv) PS C:\Users\tuser\Documents\py> pip list
Package Version
------------------ ---------
certifi 2024.8.30
charset-normalizer 3.3.2
idna 3.8
pip 21.2.3
requests 2.32.3
setuptools 57.4.0
urllib3 2.2.3
WARNING: You are using pip version 21.2.3; however, version 24.2 is available.
You should consider upgrading via the 'C:\Users\tuser\Documents\py\tenv\Scripts\python.exe -m pip install --upgrade pip' command.
(tenv) PS C:\Users\tuser\Documents\py> C:\Users\tuser\Documents\py\tenv\Scripts\python.exe -m pip install --upgrade pip --proxy http://t2rs-fgproxy.tdomain.ru:8080
(tenv) PS C:\Users\tuser\Documents\py> pip install requests --proxy http://t2rs-fgproxy.tdomain.ru:8080
Source:
# https://docs.python.org/3/library/venv.html - Creating virtual environments.
# https://realpython.com/python-coding-setup-windows/#installing-python-with-pyenv-for-windows - Installing Python With pyenv for Windows.
# https://wiki.merionet.ru/articles/kak-opredelit-versiyu-linux#:~:text=%D0%A1%D0%B0%D0%BC%D1%8B%D0%B9%20%D0%BF%D1%80%D0%BE%D1%81%D1%82%D0%BE%D0%B9%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%20%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%B8%D1%82%D1%8C%20%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8E,%D0%B2%D0%BC%D0%B5%D1%81%D1%82%D0%B5%20%D1%81%20%D0%BA%D0%BE%D0%BD%D0%BA%D1%80%D0%B5%D1%82%D0%BD%D0%BE%D0%B9%20%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B5%D0%B9%20%D1%8F%D0%B4%D1%80%D0%B0. - Как определить версию Linux?
Task:
Написание скриптов для бэкап файлов. Реализация бэкап файлов на языке Python.
Decision:
PS C:\Users\tuser\Documents\py> vim .\tpy.py
PS C:\Users\tuser\Documents\py> cat .\tpy.py
from ftplib import FTP
ftp = FTP("tipftp")
ftp.login(user="tuser", passwd="tpassword")
local_file = '/Users/tuser/Documents/py/test/testfile.txt'
#files = ftp.nlst('/Backups/tdir/*.xml')
files = ftp.nlst('/Backups/tdir/MRBTS881364.xml')
#print(files)
for file in files:
print(file)
file_list=[]
file_list.append(file)
file_list1=str(file_list)
print(file_list1)
with open(local_file, 'wb') as tempfile:
#print("Работа с файлом testfile")
#ftp.retrbinary('RETR' + file_list1, tempfile.write)
ftp.retrbinary('retr ' + file, tempfile.write)
ftp.quit()
Source:
# https://sky.pro/media/rabota-s-fajlami-v-python-kak-poluchit-spisok-vseh-fajlov-v-direktorii/
# https://docs.python.org/3/library/ftplib.html
# https://sky.pro/media/kak-ispolzovat-python-dlya-raboty-s-ftp/
# https://pythonworld.ru/tipy-dannyx-v-python/spiski-list-funkcii-i-metody-spiskov.html
# https://dvmn.org/encyclopedia/python_intermediate/python_lists/
# https://metanit.com/python/tutorial/4.1.php
# https://lavrynenko.com/python-ftplib-skachat-fajl/