Привет, Хабр! Всегда было любопытно, как автоматизировать отправку кодов через SMS для второго этапа подтверждения личности при входе пользователя. Мы с коллегой решили разработать простой, но эффективный инструмент, который мог бы автоматически генерировать и отправлять SMS с кодами пользователя. Для реализации этой задачи выбрали API сервиса МТС Exolve.
Этот сервис упрощает рассылку SMS и предоставляет удобные инструменты для работы с сообщениями. Также Exolve добавляет новым пользователям 300 рублей на счет для тестирования платформы, что в целом достаточно для того, чтобы оценить все функции сервиса без начальных инвестиций.
Как начать работу
Первый шаг для старта работы с Exolve — регистрация на официальном сайте. После неё появится доступ к личному кабинету, где можно управлять настройками и использовать различные функции платформы.
Для начала работы с API, необходимо создать приложение. Это делается во вкладке Приложения в аккаунте. Создание приложения позволит сгенерировать API ключи, необходимые для работы с SMS.
После создания приложения переходим во вкладку Ключи в настройках приложения, чтобы сгенерировать новый API ключ. Ключ будет использоваться для аутентификации ваших запросов к API Exolve.
Для отправки SMS нужен номер. Для этого используем начисленные нам при регистрации 300 рублей для тестирования платформы. Во вкладке Номера можно выбрать и приобрести номер. Также есть возможность приобрести номер по региону во вкладке фильтров.
Кратко про SMS API
SMS API Exolve имеет разнообразные методы, некоторые из них:
Метод SendSMS
Метод позволяет отправить SMS-сообщение. Нужно выполнить POST-запрос с параметрами, указывающими номер или альфа-имя отправителя. Номер в данном случае — тот номер, который мы купили, его нужно указать в формате 71234567891. .Также нужно указать номер получателя и текст сообщения. Например, запрос на Go можно сделать так:
func sendSMS(number, destination, text string) {
requestData := map[string]string{
"number": number,
"destination": destination,
"text": text,
}
jsonData, _ := json.Marshal(requestData)
req, _ := http.NewRequest("POST", "https://api.exolve.ru/messaging/v1/SendSMS", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ваш_API_ключ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
// обработка ответа...
}
Метод GetList
Метод позволяет получить данные об отправленных и полученных SMS. Он также осуществляется через POST-запрос, где можно указать фильтры для поиска сообщений:
func getList() {
// параметры запроса можно задать в зависимости от потребностей
requestData := map[string]interface{}{}
jsonData, _ := json.Marshal(requestData)
req, _ := http.NewRequest("POST", "https://api.exolve.ru/messaging/v1/GetList", bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer ваш_API_ключ")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
// Обработка ответа...
}
Помимо отправки и получения информации о сообщениях, есть методы для управления альфа-именами GetAlphaNames, создания и управления шаблонами SMS CreateTemplate, GetTemplate и GetTemplate . Подробнее с документаций можно ознакомиться здесь.
А пока перейдем к написанию генератора кодов.
Создание генератора кода
Создадим некое веб-приложение на Go, которое использует SMS API для отправки кодов верификации пользователям. Приложение будет состоять из серверной части на Go и клиентской части в виде HTML-страницы.
Сам проект организуем подобным образом:
/project-folder
│
├── main.go # файл сервера Go
├── static # папка для статических файлов
│ └── index.html # HTML файл
└──
Серверная часть (main.go)
Импорты и структура запроса
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"log"
"math/rand"
"net/http"
"sync"
"time"
"golang.org/x/time/rate"
)
// SMSRequest определяет структуру для данных запроса к SMS API
type SMSRequest struct {
Number string `json:"number"` // номер отправителя или альфа-имя
Destination string `json:"destination"` // номер получателя
Text string `json:"text"` // текст сообщения
}
Импортируемые пакеты предоставляют функции для работы с HTTP, шаблонами, случайными числами и JSON. SMSRequest — структура для упаковки данных, которые будут отправлены к SMS API.
Главная функция
var (
limiter = rate.NewLimiter(1/120.0, 1) // 1 запрос раз в две 2 минуты
mu sync.Mutex
)
func main() {
http.HandleFunc("/", serveHome)
http.HandleFunc("/send", rateLimit(handleSendSMS))
log.Println("Server started on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Здесь настроили маршрутизацию. Корневой URL обрабатывается функцией serveHome, а URL /send — функцией handleSendSMS. Также добавили ограничение по запросам.
Запуск сервера на порту 8080.
Функция serveHome
func serveHome(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("static/index.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
return
}
t.Execute(w, nil)
}
Загружает HTML-шаблон из файла и отображает его. Это входная точка для юзеров.
Функция handleSendSMS
func handleSendSMS(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
err := r.ParseForm()
if err != nil {
http.Error(w, "Failed to parse form", 400)
return
}
number := r.FormValue("number")
code := generateCode(8)
message := fmt.Sprintf("Your verification code is: %s", code)
if err := sendSMS(number, message); err != nil {
log.Printf("Failed to send SMS: %v", err)
http.Error(w, "Failed to send SMS", 500)
return
}
fmt.Fprintf(w, "SMS with code sent to %s", number)
}
Обрабатывает POST-запросы от формы на HTML-странице, а также генерирует код, формирует сообщение и отправляет SMS.
Функция generateCode
func generateCode(length int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
rand.Seed(time.Now().UnixNano())
b := make([]rune, length)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
Генерирует случайный код заданной длины.
Функция sendSMS
func sendSMS(destination, message string) error {
requestData := SMSRequest{
Number: "ВАШ_НОМЕР", // заменяем на наш купленный номер
Destination: destination,
Text: message,
}
jsonData, err := json.Marshal(requestData)
if err != nil {
return err
}
req, err := http.NewRequest("POST", "https://api.exolve.ru/messaging/v1/SendSMS", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer ваш_API_ключ") // заменяем на наш API ключ
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to send SMS: received status code %d", resp.StatusCode)
}
return nil
}
Отправляет SMS через API. Здесь важно не забыть заменить "ВАШ_НОМЕР" и "ваш_API_ключ" на ваши данные.
Функция rateLimit
func rateLimit(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
}
}
Функция для защиты от спама.
Клиентская часть (index.html)
HTML-страница предоставляет простую форму для ввода номера телефона, куда будет отправлен код.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Request SMS Code</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
form {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
input[type="text"], button {
width: 100%;
padding: 10px;
margin-top: 10px;
border: 2px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #0056b3;
color: white;
border: none;
cursor: pointer;
}
button:disabled {
background-color: #888;
cursor: not-allowed;
}
button:hover:enabled {
background-color: #004494;
}
#timer {
margin-top: 10px;
color: #d9534f;
}
</style>
</head>
<body>
<form id="smsForm" action="/send" method="post">
<h2>Введите свой номер телефона:</h2>
<input type="text" id="number" name="number" placeholder="Phone number" required>
<button type="submit" id="sendButton">Send Code</button>
<p id="timer"></p>
</form>
<script>
const sendButton = document.getElementById('sendButton');
const timer = document.getElementById('timer');
const form = document.getElementById('smsForm');
let remainingTime = 0;
form.addEventListener('submit', function(event) {
event.preventDefault();
if (remainingTime > 0) return;
sendButton.disabled = true;
remainingTime = 120;
updateTimer();
setTimeout(() => {
form.submit();
}, 1000);
const interval = setInterval(() => {
remainingTime--;
updateTimer();
if (remainingTime <= 0) {
clearInterval(interval);
sendButton.disabled = false;
}
}, 1000);
});
function updateTimer() {
if (remainingTime > 0) {
timer.textContent = `Пожалуйста, подождите ${remainingTime} секунд перед отправкой следующего запроса.`;
} else {
timer.textContent = '';
}
}
</script>
</body>
</html>
Постарался более-менее сделать красивую страничку и добавил форму с методом POST по адресу /send для отправки данных на сервер. Пользователю предлагается ввести номер телефона, это обязательное поле, и отправить его нажатием на кнопку.
А теперь запустим все это дело
Переходим в папку проекта: открываем командную строку и переходим в папку, где находится main.go. Это можно сделать с помощью команды cd:
cd C:\Users\user1\projects\project
Запускаем сервер с помощью команды go run для запуска вашего сервера:
go run main.go
Так мы запустили сервер на том порту, который указали в файле main.go и теперь можно подключиться к серверу через браузер, перейдя по адресу http://localhost:8080.
Переходим и видим нашу форму:
Вводим номер телефона, нажимаем Send Code и в консоли видим это сообщение:
После чего на номер приходит SMS:
Все работает отлично! Получили сгенерированный код.
Также важно сказать здесь о том, что тестовый баланс позволяет отправлять SMS только на номер, который вы указали при регистрации.
Что еще можно добавить
Естественно здесь уместно добавить систему логирования. В Go для этого можно использовать стандартный пакет log или более крутые решения вроде logrus или zap.
Можно расширить, добавив более детальную классификацию и обработку различных видов ошибок, возвращаемых API.
В текущем коде API-ключ и номер отправителя зашиты непосредственно в коде. В продакшене рекомендую использовать переменные окружения для хранения чувствительных данных.
Несмотря на то, что HTML-страница выполнена в целом функционально, всегда есть возможность для улучшения визуальной составляющей и пользовательского интерфейса. Можно добавить адаптивность для различных устройств и использовать фреймворки типа React или Vue.js.
Можно также добавить функции по работе с шаблонами сообщений, которые пользователь мог бы выбирать из списка, или интеграция с БД для сохранения истории отправленных сообщений, хотя у самого Exolve есть замечательная вкладка по статистике отправленных сообщений и там их можно просматривать.
В заключение
Благодаря простому API можно легко внедрять функции отправки и приема SMS в любые приложения, улучшать взаимодействие с пользователями, оптимизировать свои коммуникации и получать подробную аналитику и отчеты о результатах кампаний.
Комментарии (5)
kimisa
13.08.2024 10:35+4Я понимаю, что это туториал, но считаю что давать все-таки нужно более продвинуто с реализацией интерфейсов и структур. А не просто набор методов.
WhiteApfel
13.08.2024 10:35Причём не первая их статья (от Exolve) подобного плана (почти такая же?), и каждый раз опускается очень много важных деталей для правильной реализации.
nee77
Это все прекрасно. Но мой аккаунт у вас в сервисе уже полгода висит со статусом "тестовый", так как подписание договора с ИП и физ лицом не работает, и нет возможности пополнять баланс.
telecomgod
Да, подписание договора с ИП и физлицами пока приостановили. Доступно только для юрлиц. Но уже скоро оповестим о запуске подписания для ИП. Затем доберёмся до физлиц. Спасибо за то, что уже попробовали сервис и не оставляете нас без внимания :)