Продолжаем показывать, как работают вместе бэкенд и фронтенд. У нас уже вышло про это две статьи — как сделать простой проект телеграм-бота и как запустить с ним полноценный бэкенд. Вот краткое содержание:
- У нас есть скрипт на Python, который играет роль бэкенда — он записывает некие данные в базу и потом их выводит.
- Скрипт получает данные из фронтенда — Телеграма. В Телеграме для этого работает бот, а мостиком между Телеграмом и нашим скриптом служит специальная библиотека Python.
- На фронтенде пользователь отчитывается, что он сделал за сегодня. Эти отчёты прилетают на бэкенд, наш скрипт их принимает и записывает в базу данных.
- По специальной команде скрипт выплёвывает то, что у него сейчас есть в базе данных. Выплёвывает в бота.
При этом бэкенд не зависит от фронтенда — ему всё равно, куда отдавать данные и как их будут отображать. Воспользуемся этим и сделаем ещё один фронтенд для проекта — на этот раз в виде веб-страницы. Предположим, что это для менеджера, который хочет контролировать работу команды.
Что делаем
- Веб-страницу, внутри которой будет работать второй фронтенд.
- Внутри неё — PHP-скрипт, который будет получать данные из бэкенда.
Почему PHP? Потому что он подходит для этой простой задачи: вывести все данные из базы один раз.
Если бы нам нужно было закрыть эту страницу паролем, выдавать разные ключи разным людям, выводить данные по разным пользователям и т. д. — нужно было бы использовать Python и фреймворк Django. Но это большой труд: развернуть всё это хозяйство на сервере, а потом изучить конструкции и паттерны Django. Это имеет смысл для большого долгоиграющего проекта и не имеет для одноразовой странички.
И так всю жизнь: сначала делаешь что-то на костылях, а потом эти костыли подпирают огромные проекты за много миллиардов рублей.
# подключаем модуль для Телеграма
import telebot
# модуль работы со временем
from datetime import datetime, timezone, timedelta
# модуль для работы с базой данных
import sqlite3 as sl
# указываем токен для доступа к боту
bot = telebot.TeleBot('6285051364:AAGG2iZ77NIeWj0YjqIs791qxPhvcS3Yu0s')
# приветственный текст
start_txt = 'Привет! Это журнал «Код». \n\nТеперь у бота появился бэкенд.'
# подключаемся к файлу с базой данных
con = sl.connect('reports.db')
# открываем файл
with con:
# получаем количество таблиц с нужным нам именем
data = con.execute("select count(*) from sqlite_master where type='table' and name='reports'")
for row in data:
# если таких таблиц нет
if row[0] == 0:
# создаём таблицу для отчётов
with con:
con.execute("""
CREATE TABLE reports (
datetime VARCHAR(40) PRIMARY KEY,
date VARCHAR(20),
id VARCHAR(200),
name VARCHAR(200),
text VARCHAR(500)
);
""")
# обрабатываем старт бота
@bot.message_handler(commands=['start'])
def start(message):
# выводим приветственное сообщение
bot.send_message(message.from_user.id, start_txt, parse_mode='Markdown')
# обрабатываем команду /now
@bot.message_handler(commands=['now'])
def start(message):
# подключаемся к базе
con = sl.connect('reports.db')
# получаем сегодняшнюю дату
now = datetime.now(timezone.utc)
date = now.date()
# пустая строка для будущих отчётов
s = ''
# работаем с базой
with con:
# выполняем запрос к базе
data = con.execute('SELECT * FROM reports WHERE date = :Date;',{'Date': str(date)})
# перебираем все результаты
for row in data:
# формируем строку в общем отчёте
s = s + '*' + row[3] + '*' + ' → ' + row[4] + '\n\n'
# если отчётов не было за сегодня
if s == '':
# формируем новое сообщение
s = 'За сегодня ещё нет записей'
# отправляем общий отчёт обратно в телеграм
bot.send_message(message.from_user.id, s, parse_mode='Markdown')
# обрабатываем команду /yesterday
@bot.message_handler(commands=['yesterday'])
def start(message):
# подключаемся к базе
con = sl.connect('reports.db')
# получаем вчерашнюю дату
yesterday = datetime.today() - timedelta(days=1)
y_date = yesterday.date()
# пустая строка для будущих отчётов
s = ''
# работаем с базой
with con:
# выполняем запрос
data = con.execute('SELECT * FROM reports WHERE date = :Date;',{'Date': str(y_date)})
# смотрим на результат
for row in data:
# если результат пустой — ничего не делаем
if row[0] == 0:
pass
# если вчера были какие-то отчёты
else:
# добавляем их в общий список отчётов
s = s + '*' + row[3] + '*' + ' → ' + row[4] + '\n\n'
# если отчётов не было за вчера
if s == '':
# формируем новое сообщение
s = 'За вчерашний день нет записей'
# отправляем пользователю это новое сообщение
bot.send_message(message.from_user.id, s, parse_mode='Markdown')
# обрабатываем входящий отчёт пользователя
@bot.message_handler(content_types=['text'])
def func(message):
# подключаемся к базе
con = sl.connect('reports.db')
# подготавливаем запрос
sql = 'INSERT INTO reports (datetime, date, id, name, text) values(?, ?, ?, ?, ?)'
# получаем дату и время
now = datetime.now(timezone.utc)
# и просто дату
date = now.date()
# формируем данные для запроса
data = [
(str(now), str(date), str(message.from_user.id), str(message.from_user.username), str(message.text[:500]))
]
# добавляем с помощью запроса данные
with con:
con.executemany(sql, data)
# отправляем пользователю сообщение о том, что отчёт принят
bot.send_message(message.from_user.id, 'Принято, спасибо!', parse_mode='Markdown')
# запускаем бота
if __name__ == '__main__':
while True:
# в бесконечном цикле постоянно опрашиваем бота — есть ли новые сообщения
try:
bot.polling(none_stop=True, interval=0)
# если возникла ошибка — сообщаем про исключение и продолжаем работу
except Exception as e:
print('❌❌❌❌❌ Сработало исключение! ❌❌❌❌❌')
Запускаем скрипт бэкенда на сервере
Сейчас наш скрипт работает локально на компьютере. Если компьютер выключить или пропадёт интернет, скрипт и бот перестанут работать. Это плохо для бэкенда — он должен работать всё время и без перебоев.
Мы можем запустить скрипт на сервере — то есть на чужом компьютере, который мы арендуем за деньги. Разница в том, что сервер всё время работает и редко перезагружается.
Для запуска скрипта на сервере нам потребуется собственно арендованный виртуальный сервис. А дальше воспользуемся рецептом из нашей старой статьи про запуск бота:
- Запускаем SSH-клиент и входим на наш сервер как администраторы.
- В SSH-терминале пишем по очереди такие команды (вместо .thecode можно написать название каталога, которое вам по душе):
virtualenv .thecode
source .thecode/bin/activate - Установим Python-модуль для работы с Телеграмом:
pip install pytelegrambotapi
- Когда установка закончится, пишем такую команду (не забывайте поменять путь к файлу на свой):
python3 osebe/public_html/projects/front/backend.py
- Если хотите, чтобы после закрытия SSH-клиента или терминала бот продолжал работать, в последней команде в начале напишите
nohup
Проверим работу бота после запуска на сервере:
Бот реагирует на команды и запоминает то, что мы ему пишем, значит, бот на сервере работает как нужно.
По идее нам нужно было отправить бота в защищённую папку, например cgi-bin, чтобы никто не мог получить доступ к базе. Но для простоты проекта мы не стали тратить на это время — сделаем это отдельно и расскажем, как это работает.
Создаём страницу
Наш новый фронт — это веб-страница, поэтому напишем простой код, который создаст обычную страницу с заголовком. Создадим новый файл front.php (не HTML, это важно!) и запишем в него такой код. Это весь HTML-код, который нам понадобится, — всё остальное будем делать на PHP. Если не знаете, что такое PHP, — вот подборка для начала:
А вот код страницы:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- заголовок страницы -->
<title>Простой фронтенд к базе данных</title>
</head>
<body>
<!-- общий заголовок -->
<h1>Отчёты</h1>
</body>
</html>
Теперь кладём этот файл на сервер в ту же папку, где и python-скрипт, чтобы всё было в одном месте, и открываем страницу в браузере.
Подключаемся к базе данных
Сразу после заголовка добавляем PHP-вставку, вся магия будет происходить внутри.
<!-- начало PHP-блока -->
<?php
// конец PHP-блока
?>
Чтобы подключиться к базе данных, используем команду SQLite3() и сразу выведем сообщение об успешном подключении. Если мы его увидим — всё работает, можно двигаться дальше.
// подключаемся к базе данных
$connection = new SQLite3('reports.db');
// если подключились
if($connection){
// выводим сообщение
echo "<p>✅ Подключились к базе данных</p>";
}
Выводим отчёты
Мы будем выводить две группы отчётов: за сегодня и за остальные дни. Для этого нам нужно сделать так:
- Получить дату в нужном формате.
- Выгрузить все сегодняшние отчёты.
- Посмотреть, есть ли они вообще, если нет — выдать сообщение, что их нет.
- Если есть — вывести даты и отчёты.
- То же самое сделать для отчётов за остальные дни.
Запишем это всё на PHP — мы добавили комментарий к каждой строчке, чтобы было понятнее, как это всё работает:
// получаем дату в нужном формате
$date = date('Y-m-d');
// выводим подзаголовок
echo '<h2>За сегодня</h2>';
// получаем сегодняшние отчёты из базы
$results = $connection->query("SELECT * FROM 'reports' WHERE 'date' == $date");
// получаем количество отчётов
$row=$results->fetchArray(SQLITE3_ASSOC);
// если они есть
if ($row != false) {
// заново получаем отчёты из базы
$results = $connection->query("SELECT * FROM 'reports' WHERE 'date' == $date");
// пока есть записи в результатах поиска — выводим их
while($row=$results->fetchArray(SQLITE3_ASSOC)){
echo '<strong>' . $row['date'] . '</strong><br>';
echo 'Ник: ' . $row['name'] . '<br>';
echo 'Отчёт: ' . $row['text'] . '<br>';
echo '<br>';
}
// если отчётов нет
} else {
echo "<p>За сегодня записей нет</p>";
}
// выводим подзаголовок
echo '<h2>Остальные отчёты</h2>';
// выгружаем остальные отчёты из базы
$results = $connection->query("SELECT * FROM 'reports' WHERE 'date' != $date");
// пока есть записи в результатах поиска — выводим их
while($row=$results->fetchArray(SQLITE3_ASSOC)){
echo '<strong>' . $row['date'] . '</strong><br>';
echo 'Ник: ' . $row['name'] . '<br>';
echo 'Отчёт: ' . $row['text'] . '<br>';
echo '<br>';
}
Добавляем этот код в PHP-блок после подключения к базе, заливаем файл front.php на сервер и обновляем страницу:
Получается, что у нашего телеграм-бота появился второй фронтенд, который берёт те же данные, что и бот, но отображает их в другом месте и в другом формате.
Что дальше
Технически у нас всё сделано:
- есть бэкенд на сервере, который принимает отчёты и сохраняет их в базе;
- есть фронтенд — телеграм-бот со стандартным интерфейсом;
- есть ещё один фронтенд — веб-страница со всеми отчётами;
- всё это работает на сервере без нашего участия.
Но внутри много шероховатостей: всё ещё неоптимизированный код, небезопасное расположение базы, нет кнопок выбора дня на странице и так далее. Это можно исправлять в будущем.