Skip to content

FCM

Описание#

Является сервисом-пройслойкой, который отвечает только за один тип уведомлений - отправку push-уведомлений на мобильные устройства пользователей.

Принцип работы#

Сервис принимает gRPC-запрос SendNotification от NotificationService и вызывает соответствующий обработчик. Он, в свою очередь, вызывает обработчик SendNotification из UseCase слоя.

Модель данных в SendNotification#
    type NotificationRequest struct {
        ContactType int32 // тип контакта
        User_UUID   []string // уникальные идентификаторы пользователей
        Message     string // текст сообщения
        Title       string // заголовок
        Target      map[string]string // дополнительные параметры
    }

SendNotification (UseCase) кладёт полученные данные в буферизированный канал.

При запуске сервиса запускается worker(), что он делает:

  • Каждые 50мс проверяет очередь.
  • Берет данные из канала и вызывает для них метод sendNotification().

Что делает sendNotification():

  • Вызывает метод из repo-слоя GetTokensByUsers(), куда передает уникальные идентификаторы пользователей, тем самым получает их FCM-токены.
  • Проходится циклом по ответу БД и добавляет их в мапу, где ключом является UUID-пользователя, а значением - его FCM-токен.
  • Проходится циклом по мапе, где на каждой итерации в отдельной горутине вызывает 'SaveNotification' из repo-слоя, сохраняя уведомление в БД.
  • Далее в этом же цикле проходится ещё одним циклом по каждому FCM-токену и вызывает для него checkAuth, проверяя токен на валидность через CheckAuthService. Если токен оказался недействительным, то вызывает метод DeleteToken из repo-слоя, удаляя тем самым его из БД.
  • Вызывает метод SendPush для попытки отправки уведомления. При любой ошибке - удаляет токен из базы данных.
  • В случае успеха возвращает nil.

Что делает sendPush():

  • Проверяет поле target (доп. параметры) на их отсутствие, если их нет, то присваивает мапе значение nil (в целях экономии памяти).
  • Формирует структуру &messaging.Message{}:

        message := &messaging.Message{
            Notification: &messaging.Notification{
                Title: title, // заголовок уведомления
                Body:  body, // тело уведомления (содержание)
            },
            Token: token, // FCM-токен пользователя
            Data:  data, // дополнительные параметры (target)
        }
    

  • Вызывает у клиента (фреймворка от Google - Firebase) метод Send(), куда передаёт сформированное уведомление. В случае ошибки устанвливает статус в Span с кодом ошибки и её текстом, а также возвращает её.

  • В случае успеха возвращает nil.

Также у сервиса есть ещё три ручки: FetchNotification, WatchNotification и SaveFCMToken

WatchNotification#

Этот метод помечает уведомления пользователя как прочитанные и сразу инвалидирует кеш списка уведомлений.

Модель данных#
    type WatchNotificationRequest struct {
        User_UUID         string // уникальный идентификатор пользователя
        Notification_UUID []string // уникальный идентификатор уведомления, если null, то прочитывает все существующие уведомления
    }
  • Вызывает метод WatchNotification из UseCase, в случае возвращения ошибки, логгирует её в Span и возвращает.
  • В UseCase вызывается WatchNotification из repo-слоя, в случае ошибки - логгируется в Span и возвращается. В случае успеха - кеш удаляется, возвращается nil.
  • В repo-слое делается SQL-запрос, где у переданных уведомлений конкретного пользователя флаг is_watched меняет значение на true. Если передан пустой массив Notification_UUID - все уведомления пользователя отмечаются как прочитанные.

FetchNotification#

Этот метод позволяет получить список уведомлений.

Модель данных#
    type FetchNotificationsRequest struct {
        Filters    FetchNotificationsRequest_Filters  // фильтры
        Pagination v2.PaginationRequest               // параметры пагинации
    }
    type FetchNotificationsRequest_Filters struct {
        User_UUID string // уникальный идентификатор пользователя
    }
    type PaginationRequest struct {
        Limit *int32 // количество элементов на странице
        Page  *int32 // текущая страница
    }
  • Вызывает метод FetchNotification из UseCase, в случае ошибки - возвращает её.
  • В UseCase:
    • Создаётся уникальный ключ кеша по uuid пользователя.
    • Вызывается метод GetOrSetCache, который либо получает данные из кеша, а в случае их отсутствия в кеше - из базы данных. В случае неудачи устанавливает статус в Span с кодом и текстом ошибки, а также возвращает её.
    • Выполняется пагинация. Высчитывается начало и конец страницы, вырезаются данные из списка уведомлений по индексам start и end.
    • Формируется структура ответа FetchNotificationResponse:
          &fcm.FetchNotificationsResponse{
              Data:           result.Data, // массив уведомлений
              Pagination:     result.Pagination, // параметры пагинации
              UnwatchedCount: result.UnwatchedCount, // непрочитанные уведомления
          }
      
    • В случае успеха возвращается nil.

SaveFCMToken#

Этот метод предназначен для сохранения FCM-токена пользователя в базу данных.

Модель данных#
    type SaveFCMTokenRequest struct {
        Token     string // токен пользователя
        User_UUID string // уникальный идентификатор пользователя
        ApiKey    string // ключ авторизации конкретного устройства
    }
  • Передаёт данные в UseCase слой.
  • В UseCase
    • Формирует структуру для repo-слоя.
    • Отправляет данные в repo-слой, где они сохраняются в БД

DeleteFCMTokens#

Этот метод предназначен для удаления FCM-токенов пользователей из базы данных.

Модель данных#
type DeleteFCMTokensRequest struct {
    User_UUID string // уникальный идентификатор пользователя
}
  • Вызывает метод DeleteFCMTokens из UseCase, в случае ошибки - возвращает её.
  • В UseCase:
    • Проверяет входные данные на пустоту, в случае ошибки - возвращает её.
    • Вызывает DeleteFCMTokens из repo-слоя для удаления токенов.
    • В случае успеха - возвращает nil.