Создаем систему напоминаний о приёме лекарств

от автора

Утро, аромат свежесваренного кофе, и телефон тихонько напоминает вам о приеме важного лекарства. «Привет! Не забудь принять лекарство!» Такую систему можно реализовать самостоятельно с помощью Golang и Exolve API.

SMS остается простым, универсальным и проверенным временем методом коммуникации, работающим на любом телефоне. В этой статье я покажу, как клиники и медцентры могут построить надежную систему SMS-напоминаний.

Что понадобится:

  • Golang: язык программирования, идеально подходящий для создания серверных приложений.

  • Exolve SMS API: инструмент для отправки и управления SMS-сообщениями.

  • Docker: для контейнеризации приложения.

  • PostgreSQL: надежная БД для хранения информации о пациентах и напоминаниях.

  • ngrok: для тестирования вебхуков и локального сервера (опционально).

Предлагаю следующую структуру проекта:

sms-reminder/ ├── main.go ├── handlers/ │   └── reminder.go ├── models/ │   └── patient.go ├── utils/ │   └── exolve.go ├── config/ │   └── config.go ├── Dockerfile ├── .env └── go.mod

Теперь перейдем к реализации.

Реализация системы

Конфигурация

Сначала создадим файл config/config.go для хранения конфигурационных параметров, включая API-ключ Exolve:

package config  import (     "log"     "os"      "github.com/joho/godotenv" )  // Config структура для хранения конфигурации приложения type Config struct {     ExolveAPIKey string     Port         string     DBConnString string }  // LoadConfig загружает конфигурацию из .env файла func LoadConfig() Config {     // Загружаем переменные окружения из .env файла     err := godotenv.Load()     if err != nil {         log.Fatalf("Ошибка загрузки .env файла: %v", err)     }      // Возвращаем конфигурацию     return Config{         ExolveAPIKey: os.Getenv("EXOLVE_API_KEY"),         Port:         os.Getenv("PORT"),         DBConnString: os.Getenv("DB_CONN_STRING"),     } }

Не забываем создать .env файл с необходимыми переменными:

EXOLVE_API_KEY=Bearer your_exolve_api_key PORT=8080 DB_CONN_STRING=postgres://user:password@localhost:5432/sms_reminder?sslmode=disable

Никогда не храните конфиденциальные данные в коде. Используйте переменные окружения для защиты ключей и паролей. Добавляем .env файл в .gitignore, чтобы он не попал в систему контроля версий.

Модели

В models/patient.go опишем структуру пациента:

package models  import "time"  // Patient структура для хранения информации о пациенте type Patient struct {     ID           int       `json:"id"`            // Уникальный идентификатор пациента     Name         string    `json:"name"`          // Имя пациента     PhoneNumber  string    `json:"phone_number"`  // Номер телефона пациента     Email        string    `json:"email"`         // Электронная почта пациента     DrugName     string    `json:"drug_name"`     // Название лекарства     Dosage       string    `json:"dosage"`        // Дозировка лекарства     ReminderTime time.Time `json:"reminder_time"` // Время напоминания (в формате HH:MM:SS)     CreatedAt    time.Time `json:"created_at"`    // Время создания записи     UpdatedAt    time.Time `json:"updated_at"`    // Время последнего обновления записи }

Интегрируемся с Exolve API

В utils/exolve.go реализуем функции для взаимодействия с Exolve SMS API:

package utils  import (     "bytes"     "encoding/json"     "fmt"     "io/ioutil"     "net/http" )  // ExolveClient структура для взаимодействия с Exolve API type ExolveClient struct {     APIKey string // API-ключ для авторизации }  // NewExolveClient создает новый экземпляр ExolveClient func NewExolveClient(apiKey string) *ExolveClient {     return &ExolveClient{APIKey: apiKey} }  // SendSMSRequest структура запроса для отправки SMS type SendSMSRequest struct {     Number              string  `json:"number"`                        // Номер отправителя или альфа-имя     Destination         string  `json:"destination"`                   // Номер получателя     Text                string  `json:"text"`                          // Текст сообщения     TemplateResourceID  *uint64 `json:"template_resource_id,omitempty"` // Идентификатор шаблона (опционально) }  // SendSMSResponse структура ответа от Exolve API при отправке SMS type SendSMSResponse struct {     MessageID string `json:"message_id"` // Уникальный идентификатор сообщения }  // SendSMS отправляет SMS-сообщение через Exolve API func (c *ExolveClient) SendSMS(req SendSMSRequest) (SendSMSResponse, error) {     url := "https://api.exolve.ru/messaging/v1/SendSMS" // Точка подключения API     jsonData, err := json.Marshal(req) // Преобразуем запрос в JSON     if err != nil {         return SendSMSResponse{}, fmt.Errorf("ошибка маршалинга запроса: %v", err)     }      // Создаем новый HTTP-запрос     httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))     if err != nil {         return SendSMSResponse{}, fmt.Errorf("ошибка создания HTTP-запроса: %v", err)     }      // Устанавливаем заголовки     httpReq.Header.Set("Authorization", c.APIKey)     httpReq.Header.Set("Content-Type", "application/json")      client := &http.Client{}     resp, err := client.Do(httpReq) // Отправляем запрос     if err != nil {         return SendSMSResponse{}, fmt.Errorf("ошибка отправки HTTP-запроса: %v", err)     }     defer resp.Body.Close()      body, err := ioutil.ReadAll(resp.Body) // Читаем тело ответа     if err != nil {         return SendSMSResponse{}, fmt.Errorf("ошибка чтения ответа: %v", err)     }      if resp.StatusCode != http.StatusOK { // Проверяем статус ответа         return SendSMSResponse{}, fmt.Errorf("Exolve API ошибка: %s", string(body))     }      var sendResp SendSMSResponse     err = json.Unmarshal(body, &sendResp) // Парсим ответ     if err != nil {         return SendSMSResponse{}, fmt.Errorf("ошибка парсинга ответа: %v", err)     }      return sendResp, nil // Возвращаем результат }

ExolveClient — структура для работы с Exolve API, включая отправку SMS через метод SendSMS. Метод формирует JSON-запрос, отправляет его с нужными заголовками, обрабатывает ответ и извлекает MessageID.

Обработчики

В handlers/reminder.go создадим обработчики для добавления пациента и отправки напоминаний:

package handlers  import (     "database/sql"     "encoding/json"     "log"     "net/http"     "time"      "sms-reminder/models"     "sms-reminder/utils"      _ "github.com/lib/pq" // Драйвер PostgreSQL )  // ReminderHandler структура для обработки HTTP-запросов type ReminderHandler struct {     DB           *sql.DB            // Подключение к базе данных     ExolveClient *utils.ExolveClient // Клиент для взаимодействия с Exolve API }  // NewReminderHandler создает новый экземпляр ReminderHandler func NewReminderHandler(db *sql.DB, exolveClient *utils.ExolveClient) *ReminderHandler {     return &ReminderHandler{         DB:           db,         ExolveClient: exolveClient,     } }  // AddPatient обрабатывает POST-запрос для добавления нового пациента func (h *ReminderHandler) AddPatient(w http.ResponseWriter, r *http.Request) {     var patient models.Patient      // Декодируем JSON-запрос в структуру Patient     err := json.NewDecoder(r.Body).Decode(&patient)     if err != nil {         http.Error(w, "Неверный формат запроса", http.StatusBadRequest)         return     }      // Валидация входных данных     if patient.Name == "" || patient.PhoneNumber == "" || patient.DrugName == "" || patient.Dosage == "" || patient.ReminderTime.IsZero() {         http.Error(w, "Не все обязательные поля заполнены", http.StatusBadRequest)         return     }      // SQL-запрос для вставки нового пациента в базу данных     query := `INSERT INTO patients (name, phone_number, email, drug_name, dosage, reminder_time, created_at, updated_at)               VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW()) RETURNING id`          // Выполняем запрос и получаем ID нового пациента     err = h.DB.QueryRow(query, patient.Name, patient.PhoneNumber, patient.Email, patient.DrugName, patient.Dosage, patient.ReminderTime.Format("15:04:05")).Scan(&patient.ID)     if err != nil {         log.Printf("Ошибка вставки в базу данных: %v", err)         http.Error(w, "Ошибка базы данных", http.StatusInternalServerError)         return     }      // Устанавливаем статус ответа и отправляем информацию о пациенте     w.WriteHeader(http.StatusCreated)     json.NewEncoder(w).Encode(patient) }  // SendReminders обрабатывает GET-запрос для отправки напоминаний func (h *ReminderHandler) SendReminders(w http.ResponseWriter, r *http.Request) {     currentTime := time.Now().Format("15:04:05") // Получаем текущее время в формате HH:MM:SS      // SQL-запрос для выборки пациентов, у которых время напоминания совпадает с текущим временем     query := `SELECT id, name, phone_number, drug_name, dosage FROM patients WHERE reminder_time = $1`     rows, err := h.DB.Query(query, currentTime)     if err != nil {         log.Printf("Ошибка выборки из базы данных: %v", err)         http.Error(w, "Ошибка базы данных", http.StatusInternalServerError)         return     }     defer rows.Close()      // Обрабатываем каждого пациента и отправляем ему SMS-напоминание     for rows.Next() {         var p models.Patient         err := rows.Scan(&p.ID, &p.Name, &p.PhoneNumber, &p.DrugName, &p.Dosage)         if err != nil {             log.Printf("Ошибка сканирования строки: %v", err)             continue         }          // Формируем текст напоминания         message := "Привет " + p.Name + "! Это напоминание принять ваше лекарство: " + p.DrugName + ". Дозировка: " + p.Dosage + "."          // Создаем запрос для отправки SMS         sendReq := utils.SendSMSRequest{             Number:      "YourSenderNumberOrAlphaName", // Замените на ваш номер или альфа-имя             Destination: p.PhoneNumber,             Text:        message,         }          // Отправляем SMS через Exolve API         sendResp, err := h.ExolveClient.SendSMS(sendReq)         if err != nil {             log.Printf("Не удалось отправить SMS на %s: %v", p.PhoneNumber, err)             continue         }          // Логируем успешную отправку         log.Printf("Напоминание отправлено пациенту %s, MessageID: %s", p.PhoneNumber, sendResp.MessageID)     }      // Отправляем ответ о завершении процесса     w.WriteHeader(http.StatusOK)     w.Write([]byte("Напоминания отправлены")) }

ReminderHandler управляет подключением к базе данных и клиентом Exolve для отправки SMS. Конструктор NewReminderHandler создает экземпляр обработчика. Метод AddPatient принимает POST-запрос, валидирует данные, сохраняет пациента в базу данных и возвращает его информацию. Метод SendReminders обрабатывает GET-запрос, выбирает пациентов с актуальным временем напоминания, отправляет SMS через Exolve API и логирует результаты.

Основной файл

В main.go настроим маршрутизацию и запуск сервера:

package main  import (     "database/sql"     "log"     "net/http"      "sms-reminder/config"     "sms-reminder/handlers"     "sms-reminder/utils"      "github.com/gorilla/mux"     _ "github.com/lib/pq" // Драйвер PostgreSQL )  func main() {     // Загружаем конфигурацию из .env файла     cfg := config.LoadConfig()      // Подключаемся к базе данных     db, err := sql.Open("postgres", cfg.DBConnString)     if err != nil {         log.Fatalf("Не удалось подключиться к базе данных: %v", err)     }     defer db.Close()      // Проверяем подключение к базе данных     err = db.Ping()     if err != nil {         log.Fatalf("Не удалось установить соединение с базой данных: %v", err)     }      log.Println("Успешно подключились к базе данных")      // Создаем клиента для Exolve API     exolveClient := utils.NewExolveClient(cfg.ExolveAPIKey)      // Создаем обработчик напоминаний     reminderHandler := handlers.NewReminderHandler(db, exolveClient)      // Настраиваем маршруты с использованием Gorilla Mux     r := mux.NewRouter()     r.HandleFunc("/add-patient", reminderHandler.AddPatient).Methods("POST")         // Маршрут для добавления пациента     r.HandleFunc("/send-reminders", reminderHandler.SendReminders).Methods("GET")     // Маршрут для отправки напоминаний      // Запускаем сервер     log.Printf("Сервер запускается на порту %s", cfg.Port)     if err := http.ListenAndServe(":"+cfg.Port, r); err != nil {         log.Fatalf("Не удалось запустить сервер: %v", err)     } }

Загружаем настройки из .env с помощью LoadConfig, подключаемся к PostgreSQL и инициализируем Exolve клиент. Создаем ReminderHandler для работы с базой данных и API, настраиваем маршруты для добавления пациентов и отправки напоминаний через gorilla/mux. Запускаем HTTP-сервер и слушаем запросы.

Dockerfile

Для удобства развертывания добавим Dockerfile:

# Используем официальный образ Golang в качестве базового FROM golang:1.20-alpine  # Устанавливаем рабочую директорию внутри контейнера WORKDIR /app  # Копируем файлы зависимостей и устанавливаем их COPY go.mod . COPY go.sum . RUN go mod download  # Копируем весь исходный код в контейнер COPY . .  # Собираем приложение RUN go build -o main .  # Указываем порт, который будет использоваться EXPOSE 8080  # Определяем команду для запуска приложения CMD ["./main"]

Используем легковесный Alpine образ с Golang, настраиваем рабочую директорию, копируем go.mod и go.sum для установки зависимостей, переносим исходный код, компилируем приложение, открываем порт 8080 и задаем команду запуска контейнера.

Создадим базу данных

Перед запуском приложения создадим базу данных и таблицу для хранения информации о пациентах. Ниже приведен пример SQL-скрипта для создания таблицы patients:

CREATE DATABASE sms_reminder;  \c sms_reminder  CREATE TABLE patients (     id SERIAL PRIMARY KEY,     name VARCHAR(255) NOT NULL,     phone_number VARCHAR(20) NOT NULL,     email VARCHAR(255),     drug_name VARCHAR(255) NOT NULL,     dosage VARCHAR(255) NOT NULL,     reminder_time TIME NOT NULL,     created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT (NOW() AT TIME ZONE 'UTC'),     updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT (NOW() AT TIME ZONE 'UTC') );  -- Добавляем индекс для быстрого поиска по времени напоминания CREATE INDEX idx_reminder_time ON patients(reminder_time);

Создаем базу данных sms_reminder и таблицу patients, которая хранит информацию о пациентах и времени напоминаний. Добавляем индекс на поле reminder_time для ускорения запросов по времени напоминания.

Тестирование

Соберем и запустим Docker-контейнер:

docker build -t sms-reminder . docker run -d -p 8080:8080 --env-file .env sms-reminder

Отправим POST-запрос для добавления нового пациента:

curl -X POST http://localhost:8080/add-patient \ -H "Content-Type: application/json" \ -d '{     "name": "Иван Иванов",     "phone_number": "79991234567",     "email": "ivan@example.com",     "drug_name": "Парацетамол",     "dosage": "500 мг",     "reminder_time": "08:00:00" }'

Ожидаемый ответ:

{     "id": 1,     "name": "Иван Иванов",     "phone_number": "79991234567",     "email": "ivan@example.com",     "drug_name": "Парацетамол",     "dosage": "500 мг",     "reminder_time": "08:00:00",     "created_at": "2024-12-18T07:59:59Z",     "updated_at": "2024-12-18T07:59:59Z" }

Отправка напоминаний

Отправим GET-запрос для отправки напоминаний:

curl http://localhost:8080/send-reminders

Ожидаемый ответ:

Напоминания отправлены

В логах контейнера можно увидеть что-то вроде:

Напоминание отправлено пациенту 79991234567, MessageID: 439166538239448536

После отправки напоминаний, можно будет проверить отправленные SMS-сообщения в панели управления Exolve.

Заключение

Не останавливайтесь на достигнутом! Вы можете расширить функции системы, добавив, например, отправку напоминаний по электронной почте или интеграцию с мобильными приложениями. Возможности безграничны, и только ваше воображение задает рамки.

Удачи в ваших начинаниях!


Подписывайтесь на наш Хаб, следите за новыми гайдами и получайте приз

Каждый понедельник мы случайным образом выбираем победителей среди новых подписчиков нашего Хабр-канала и дарим крутые призы от МТС Exolve: стильные рюкзаки, лонгсливы и мощные беспроводные зарядки. Победители прошлых розыгрышей и правила.


ссылка на оригинал статьи https://habr.com/ru/articles/869928/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *