Статьи

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

Конечный продукт
Что вы будете создавать

Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения

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

Если вы еще не опробовали Планировщик собраний, запланируйте свою первую встречу . Когда ваши коллеги и друзья ответят, вы увидите уведомления в действии. Я принимаю участие в комментариях ниже, поэтому, пожалуйста, поделитесь своими отзывами. Мне особенно интересно, если вы хотите предложить новые функции или темы для будущих уроков.

Напоминаем, что весь код для Meeting Planner написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 .

С помощью MeetingLog из предыдущего эпизода мы будем регулярно отслеживать, когда изменения устаревают несколько минут, и объединять их в единое обновление для другого участника (ов) или организатора собрания.

Наш процесс DaemonController actionFrequent будет проверять наличие обновлений каждые несколько минут:

1
2
3
4
public function actionFrequent() {
 // called every three to five minutes
 // notify users about fresh changes
 Meeting::findFresh();

Meeting::findFresh() просматривает журнал для записей, которые старше, чем MeetingLog::TIMELAPSE , в настоящее время пять минут. Когда он находит их, он смотрит на каждого из участников, вовлеченных в внесение изменений, и уведомляет их через Meeting::notify() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static function findFresh() {
  // identify all meetings with log entries not yet cleared
  $meetings = Meeting::find()->where(‘logged_at-cleared_at>0’)->all();
  foreach ($meetings as $m) {
    // to do — choose a different safe gap, for now an hour
    if ((time()-$m->logged_at)>3600) {
      // to do — consider clearing out these old ones
      continue;
    }
    // uncleared log entry older than TIMELAPSE
    if ((time()-$m->logged_at) > MeetingLog::TIMELAPSE) { //
      $logs = MeetingLog::find()->where([‘meeting_id’=>$m->id])->groupBy(‘actor_id’)->all();
      $current_actor=0;
      foreach ($logs as $log) {
        if ($log->actor_id<>$current_actor) {
           $current_actor = $log->actor_id;
          // new actor, let’s notify others
          if ($log->actor_id==$m->owner_id) {
            // this is the organizer
            // notify the participants
            //echo ‘notify participants’;
            foreach ($m->participants as $p) {
               $m->notify($m->id,$p->id);
            }
          } else {
            // this is a participant
            // notify the organizer and
            // to do — when there are multiple participants
            $m->notify($m->id,$m->owner_id);
          }
        } else {
          // this log entry by same actor as last
          continue;
        }
      }
      // clear the log for this meeting
      Meeting::clearLog($m->id);
    }
  }
}

Если организатор собрания внес изменение, то участник (ы) должен быть уведомлен. Если участник внес изменение, организатор (и в будущем другие участники) должны быть уведомлены; несколько встреч участников будут реализованы в последующих уроках.

В текущем пользовательском интерфейсе Meeting Planner ряд зарегистрированных изменений ссылаются на идентичные элементы и отменяют друг друга. Например, если вы щелкнете по переключателю для какого-либо места, оно сначала будет отклонено. Вы должны нажать на нее еще раз, чтобы принять его. Оба изменения зарегистрированы.

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

Например, вот история MeetingLog действий. Обратите внимание на повторение действий Cloudster, которые отменяют друг друга в тех же местах и ​​времени:

Уведомления планировщика собраний - пример MeetingLog История действий

Основное текстовое представление скажет:

Cloudster добавил примечание Спасибо за напоминание. Я буду уверен., Принятое время четверг 9 июня в 12:00 вечера, принятое время пт 10 июня в 12:00 вечера, отклоненное время пт 10 июня в 12:00 вечера, отклоненное время четверг 9 июня в 12:00 PM, время отклонено ср. 8 июня в 12:30, отклонено место Chaco Canyon Organic Cafe, принято место Chaco Canyon Organic Cafe, отклонено место Chaco Canyon Organic Cafe, принято место No Bones Beach Club и отклонено место No Bones Beach Club.

Как создать простую текстовую сводку итоговых изменений, которые они внесли в электронное письмо с уведомлением, как показано ниже:

Уведомления организатора собрания - итоговое уведомление по электронной почте с текстовой сводкой

В Meeting::notify() мы запрашиваем историю активности этой встречи с момента последнего уведомления:

1
2
// build the english language notification
$history = MeetingLog::getHistory($meeting_id,$user_id,$mtg->cleared_at);

Вот код, который строит текстовую строку того, что произошло. В будущем я, скорее всего, сделаю полировку, чтобы очистить ясность описания. Например, окончательная версия предоставляет правильный грамматический список с соответствующим использованием запятых, а затем ‘и’ в последний раз.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static function getHistory($meeting_id,$user_id,$cleared_at) {
  // build a textual history of events for this meeting
  // not performed by this user_id and since cleared_at
  $str =»;
  $events = MeetingLog::find()->where([‘meeting_id’=>$meeting_id])->andWhere(‘actor_id<>’.$user_id)->andWhere(‘created_at>’.$cleared_at)->orderBy([‘created_at’ => SORT_DESC,’actor_id’=>SORT_ASC])->all();
  $num_events = count($events);
  $cnt =1;
  $current_actor = 0;
  $current_str=»;
  $items_mentioned =[];
  foreach ($events as $e) {
    if ($e->actor_id <> $current_actor) {
      // new actor, update the overall string
      $str.=$current_str.'<br />’;
      // reset the current actor’s event string
      $current_str=»;
      $current_actor = $e->actor_id;
      $actor = MiscHelpers::getDisplayName($e->actor_id);
    } else {
        $actor = »;
    }
    $action = $e->getMeetingLogCommand();
    $item = $e->getMeetingLogItem();
    if (in_array($e->item_id,$items_mentioned)) {
      // only mention item the first time it appears (last action, as sorted)
      continue;
    } else {
      $items_mentioned[]=$e->item_id;
      if ($actor==») {
          if ($cnt == $num_events) {
            $current_str.=’ and ‘.$action.’
          } else {
            $current_str.=’, ‘.$action.’
          }
      } else {
          $current_str.=$actor.’
      }
      // count events
      $cnt+=1;
    }
  }
  // add last current_str (may be empty)
  $str.=$current_str.'<br />’;
  return $str;
}

По сути, getHistory () зависит от пользователя. Приведенный ниже запрос сортирует действия в обратном порядке, потому что в основном последнее изменение имеет доминирующее влияние:

1
$events = MeetingLog::find()->where([‘meeting_id’=>$meeting_id])->andWhere(‘actor_id<>’.$user_id)->andWhere(‘created_at>’.$cleared_at)->orderBy([‘created_at’ => SORT_DESC,’actor_id’=>SORT_ASC])->all();

В будущем будет несколько участников, чьи действия перекрываются во времени, поэтому мы группируем историю по actor_id .

Затем я отслеживаю $current_actor при создании текстуализации, поэтому мы упоминаем только их имя один раз, то есть «Джефф сделал эти действия. Джон сделал эти действия», а не «Джефф сделал это, Джон сделал это, Джефф сделал это и Джон сделал это, и Джон сделал это «.

Точно так же я отслеживаю упоминание объектов в $items_mentioned и игнорирую более ранние события, предоставляя только последнее, доминирующее действие в определенном месте или времени, то есть «Джефф принял No Bones Beach Club», а не «Jeff отклонил No Bones Beach Club, Джефф принял No Bones Beach Club. «

Код был сложным и забавным для написания. Получившаяся текстуализация (показанная выше) интересно наблюдать.

В руководстве по уточнению шаблонов электронной почты я описал переход на наши новые адаптивные шаблоны Oxygen. Это потребовало пересмотра шаблона notify-html.php и потребовало постепенной полировки с течением времени.

Вот выдержка из шаблона:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<tr>
  <td align=»center» valign=»top» width=»100%» style=»background-color: #f7f7f7;»
  <center>
    <table cellspacing=»0″ cellpadding=»0″ width=»600″ class=»w320″>
      <tr>
        <td class=»header-lg»>
          Changes to Your Meeting
        </td>
      </tr>
      <tr>
        <td class=»free-text»>
          <p>Hi <?php echo Html::encode(MiscHelper::getDisplayName($user->id));
          <p><?php echo $history;
          <p>Please click the button below to view the meeting page.</p>
        </td>
      </tr>
      <tr>
        <td class=»button»>
          <div><!—[if mso]>
            <v:roundrect xmlns:v=»urn:schemas-microsoft-com:vml» xmlns:w=»urn:schemas-microsoft-com:office:word» href=»http://» style=»height:45px;v-text-anchor:middle;width:155px;»
              <w:anchorlock/>
              <center style=»color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;»>My Account</center>
            </v:roundrect>
          <![endif]—><a class=»button-mobile» href=»<?php echo $links[‘view’] ?>»
          style=»background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:’Cabin’, Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;»>Visit Meeting Page</a></div>
        </td>
      </tr>
    </table>
  </center>
</td>
</tr>

Доставка электронной почты осуществляется аналогично нашим приглашениям на встречи с расширением Yii2 SwiftMailer через нашу конфигурацию Mailgun SMTP.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
$message = Yii::$app->mailer->compose([
  ‘html’ => ‘notify-html’,
  ‘text’ => ‘notify-text’
],
[
  ‘meeting_id’ => $mtg->id,
  ‘sender_id’=> $user_id,
  ‘user_id’ => $a[‘user_id’],
  ‘auth_key’ => $a[‘auth_key’],
  ‘links’ => $links,
  ‘meetingSettings’ => $mtg->meetingSettings,
  ‘history’=>$history,
]);
if (!empty($a[’email’])) {
  $message->setFrom([‘[email protected]’=>’Meeting Planner’]);
  $message->setReplyTo(‘mp_’.$meeting_id.’@meetingplanner.io’);
  $message->setTo($a[’email’])
      ->setSubject(Yii::t(‘frontend’,’Meeting Update: ‘).$mtg->subject)
      ->send();
}

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

Попробуйте использовать функцию уведомлений, запланировав свою первую встречу . Вы должны получать регулярные уведомления, как ваши приглашенные отвечают. Кроме того, я буду признателен, если вы поделитесь своим опытом ниже в комментариях, и мне всегда интересны ваши предложения. Вы также можете связаться со мной через Twitter @reifman напрямую.

Я также начинаю экспериментировать с WeFunder, основываясь на внедрении новых правил краудфандинга SEC . Пожалуйста, обратите внимание на наш профиль . Я могу написать об этом больше как часть нашей серии.

Следите за будущими уроками в серии « Построение стартапа с помощью PHP» . Есть еще несколько важных функций.