Создаем систему напоминаний о приёме лекарств
Утро, аромат свежесваренного кофе, и телефон тихонько напоминает вам о приеме важного лекарства. «Привет! Не забудь принять лекарство!» Такую систему можно реализовать самостоятельно с помощью 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: стильные рюкзаки, лонгсливы и мощные беспроводные зарядки. Победители прошлых розыгрышей и правила.