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