Обратите внимание, что новости можно получать по RSS.
X
-

Информационные технологии, LiveJournal cr_it - архив

3 декабря 2010, 12:55 (5315 дней назад, №8807)Приложение для социальных сетей - SMS Пушка
По работе написал приложение SMS Direct для соцсетей (aka SMS Пушка), которое предоставляет возможность посылать SMS из соцсети на указанный номер, а также получать ответы, просматривать историю входящих и исходящих сообщений и прочее.
За год приложение установило полмиллиона пользователей и теперь его решили развивать в сторону версии для групп, уже другие люди. Пока свежи воспоминания, хочу немного поделиться полученным опытом разработки и работы с основными нашими социальными сетями (ВКонтакте, Мой Мир, Facebook, Одноклассники).

Технологии

Приложение состоит из клиентской части (единой для всех соцсетей), написанной на Flex, и серверной, на PHP + PostgreSQL.

Обмен данными между клиентом и сервером осуществляется в виде бинарных AMF3 пакетов поверх HTTP. Для Flex этот формат (Action Message Format) является родным, а в PHP для распаковки и упаковки данных используется AMFPHP (хотя, возможны варианты - ZendAMF, SabreAMF, WebORB).

При реализации клиента использовался EasyMVC. Хотя сейчас, пожалуй, я бы использовал Cairngorm - можно проще и аккуратнее добавлять новую функциональность.

Все обращения к серверной части выглядят как dispatchEvent(new GetUserInfEvent(Model.GET_USERINF_EVENT_TYPE), params);

Соответственно, в Controller’е это событие ловится и выполняется gateway.call("phpService.getUserInf", new Responder(onResult, onFault), evt).

По факту при этом осуществляется HTTP POST запрос к серверу, просто название метода и параметры вызова упакованы в AMF. На стороне сервера AMFPHP распаковывает всё обратно и на входе в function getUserInf($v) получаем ассоциативный массив с переданными параметрами. В конец метода делаем return $result и происходит обратная процедура - в результате чего, в клиенте в onResult() получаем объект, соответствующий массиву $result.

Серверная часть состоит из двух скриптов:

Первый запускается раз в минуту по крону, просматривает базу на предмет ещё не отправленных сообщений (если такие есть - отправляет) и опрашивает SMS гейт по-поводу новых входящих (если есть, помещает их в базу).

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

Здесь стоит остановиться на том, как обеспечивается контекст для этих операций (сессия текущего пользователя):

В клиентской части все event’ы наследуются от CommonUserEvent. Поэтому, к примеру,  DispatchEvent (.. MarkMessagesInEvent ..) автоматически передаёт user_id / net_id текущего пользователя приложения внутрь MarkMessagesInEvent и далее - в handler_MarkMessagesInEvent, откуда уже происходит gateway.call(), в итоге отсылающий в одном AMF пакете данные специфичные для MarkMessagesInEvent (id помечаемого сообщения) и контекст (user_id / net_id).

На сервере первой строчкой любого метода является вызов UpdateUserInfo(), который достаёт из базы всё необходимую информацию о текущем пользователе по переданным user_id / net_id (если такого пользователя в базе ещё нет, он создаётся).

Интеграция с социальными сетями

Может некоторые из перечисленных социальных сетей чего-то там друг у друга и копировали но, к большому сожалению, это не касается API. Каждый делал так, как ему показалось правильным. Поскольку поддерживать четыре разных приложения не слишком интересное занятие (равно как и создание промежуточного SWF, который подгружал бы в себя основной), было решено сделать приложение универсальным.

По существу, от SWF клиента требовалось не так уж и много - он должен получить от сети id пользователя, его имя и признак установки приложения. id и признак установки получаются автоматически, так как передаются в виде flashvars - никакого API задействовать не надо.

С именем - не так (кстати, весьма жаль, что оно не передаётся также через flashvars - ведь очень многим приложениям трёх перечисленных параметров вполне достаточно - зачем лишний раз дёргать API..)

Итак, первым делом клиент должен определить, в какую социальную сеть он попал. Определяем по параметрам, которые передаются SWF’ке:

if ( (parameters.vid!='') && (parameters.vid!=undefined) )
     user_net_id = Model.MAILRU;
else if ( (parameters.api_url!='') && (parameters.api_url!=undefined) )
     user_net_id = Model.VKONTAKTE;
else if ( (parameters.fb_sig!='') && (parameters.fb_sig!=undefined) )
     user_net_id = Model.FACEBOOK;
else if ( (parameters.sig!='') && (parameters.sig!=undefined) )
     user_net_id = Model.ODNOKLASSNIKI;
else
{
     user_net_id = Model.NOWHERE;
}//else


Определяем id пользователя (viewer_id) и признак установки приложения на страницу пользователя:

Одноклассники:   parameters.logged_user_id, parameters.authorized
Facebook:   parameters.fb_sig_user, parameters.fb_sig_added
Мой Мир:   parameters.vid, parameters.is_app_user
ВКонтакте:   parameters.viewer_id, parameters.is_app_user

Теперь, нужно получить имя пользователя. Как я уже сказал, реализация API везде сильно отличается. Где-то достаточно простого GET запроса, где-то надо сначала установить соединение, дождаться когда оно будет установлено и только потом запрашивать имя (Facebook, Мой Мир).

Раньше из соцсети брался ещё один параметр - timezone. В старой версии приложения время отправки сообщения можно было задавать в формате ЧАСЫ:МИНУТЫ, но от этого пришлось отказаться и теперь используется формат ЧЕРЕЗ СКОЛЬКО ЧАСОВ. Причина в том, что нет возможности достоверно узнать таймзону пользователя. Из социальных сетей её возвращает только ВКонтакт и Facebook, причём в обоих случаях эта информация слишком часто оказывается неверной: неправильно указан город (либо правильно, но для него у них в базе неверная таймзона), неверно учтено летнее/зимнее время для данного города  и т.д. Была попытка подходить к вопросу иначе - смотреть разницу во времени между компьютером пользователя и нашим сервером. Однако, после сбора статистики от этого тоже пришлось отказаться - слишком часто Flash возвращает неправильное время/таймзону пользователя. В итоге, было решено указывать через сколько часов отсылать сообщение (считая от момента нажатия кнопки “Отправить”). Кстати, думаю и в ленте Facebook время и дата фигурирует в виде “16 hours ago” не случайно.

Ещё, взависимости от сети, можно использовать некоторые её дополнительные возможности. Так например, во ВКонтакте и в Фейсбуке можно выводить слева, рядом с коротким именем приложения, число новых сообщений. В Моём Мире есть понятие “виджет” - можно выводить на странице пользователя кусок HTML кода (опять же - для показа количества новых сообщений). В случае с ВКонтактом ещё можно получать и показывать их рекламные объявления.

Впечатления от соцсетей

В ходе работы сложилось определённое впечатление о каждой из четырёх соцсетей. Точнее, о тех аспектах, с которыми мне пришлось столкнуться.

ВКонтакте - наиболее устойчивая, с наименьшим количеством глюков. Однако и наименее предсказуемая. Без всяких предупреждений меняются ключевые правила игры - никаких roadmap’ов, уведомлений за несколько месяцев (как, допустим, в Facebook). Прямое взаимодействие между разработчиками и администрацией формально не предусмотрено - всё зависит от везения и случайностей.
API хорошо документировано, разобраться несложно. Много разработчиков, соответственно нетрудно найти ответ на любой вопрос.
При запросах к API ВКонтакта следует учитывать, что их сервера могут регулярно притормаживать с ответом. По моим измерениям, задержка с ответом в 3-4 секунды и более - это регулярное явление. В среднем, такое происходит чаще раза в минуту. Т.е. запросы нужно обрывать по какому-то разумному таймауту. И помнить, что если запросы происходят слишком часто (>3 раз в сек), возвращается “Too many requests”.

Facebook - очень много багов, подчас просто удивительных. Второстепенные не исправляются годами. Однако, существует публичный bugtracker, так что всегда можно посмотреть, является ли твоя личная проблема багом, и знают ли о нём фейсбуковцы. В последнее время они начали с багами бороться, но баги пока что побеждают. Регулярно происходят серьёзные изменения, однако о них заранее предупреждают в roadmap’e.
Возможностей связаться с администрацией ещё меньше, чем в случае с ВКонтактом (не считая bugtracker’a). Однако, опять же, в последнее время они озаботились этой проблемой.
API на недосягаемой высоте. Но документация постоянно находится в устаревшем состоянии. Также много разработчиков, можно найти много примеров.
Важная особенность Facebook’a - лишь в последнее время наблюдается приток туда русскоязычного населения. Да и то, чаще всего это люди, которые общаются с иностранцами или теми, кто живёт и работает за рубежом.

Мой Мир - пока всё весьма сырое и с массой проблем, однако разработчики оперативно реагируют, общаются, пытаются что-то предпринимать.  API очень скромное и несколько сумбурное. Документация лаконичная, примеров доступно мало. В целом, на мой взгляд, сеть довольно странная, однако своя аудитория у неё есть - это совершенно однозначно.

Одноклассники - тут сложная ситуация. Закрытая структура - требуют заключения договора. На каждый чих и изменение нужно обращаться к администрации (правда, реагируют очень быстро - для разработчика открывается аккаунт в JIRA и там происходит всё общение). Очень строгие требования к приложениям. До одобрения приложение можно запускать только в совершенно отдельной “песочнице”. Затем, после разрешения, на боевом сайте, но видимым только для конкретных людей. Документация довольно скромная, разработчиков немного, соответственно примеры найти сложно. Багов и недоработок - хватает. Ситуация с API в чём-то напоминает Мой Мир.
Способны передумывать. Хотя наше приложение было полностью оттестировано и готово к запуску, пришлось пока временно приостановить работу с ними.

В итоге, наиболее успешно приложение пошло, конечно, ВКонтакте. Основные рассуждения и  графики в этой статье - по ВКонтакту, где набрано более 500 тысяч человек. В Моём Мире - более 10 тысяч. В Facebook’е - незначительное количество, Одноклассники пока заморожены.
Здесь, правда, следует учитывать, что не использовалась агрессивная реклама (типа постингов на стены друзей и пр.) и, как таковой, не было цели набрать как можно больше пользователей за наименьшее время. Наоборот - сначала смотрели на нагрузку и как это всё будет работать.

О структуре базы

Всё достаточно тривиально, я лишь немного остановлюсь на вопросе поддержки несколько соцсетей.

Вводится понятие net_id - идентификтор социальной сети. Таким образом, каждый пользователь идентифицируется парой user_id / net_id.  Чтобы не таскать net_id по всем таблицам, пара user_id / net_id существует только в таблице users, где ей соответствует user_id_int (users.id SERIAL). По user_id_int линкуются остальные таблицы - сообщений, контактов и пр.
Немаловажно, что user_id должен иметь тип DECIMAIL (20,0) - никаким UNSIGNED INT не обойтись, из-за гигантских идентификаторов в части соцсетей (планируют расширяться на видимую часть Вселенной ;) В скриптах, однако, с такими идентификаторами можно работать уже только как со строками - никаких float или Number не хватит.

Отмечу особо, что один и тот же человек в разных социальных сетях хранится в базе как два разных пользователя - т.е. двумя разными строчками в таблице users. Нет необходимости связывать их.

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

Дизайн и интерфейс

Интерфейс был нарисован сначала в виде наброска в Balsamiq Mockup, а затем реализован в виде mxml + actionscript.

Основано всё на компоненте TabNavigator, который весьма удобен для работы и понятен пользователям. Хотя, нужно оговориться, что во Flex 4 среди Spark компонент его нет - если не хочется мешать Halo и Spark (не советую) то придется что-то искать или изобретать самому.

Из особенностей:

Для того чтобы пользователи не нажимали много раз подряд “Отправить”, кнопка становится неактивной, пока а) не пройдет пара секунд И б) не будет изменен текст сообщения.

Помимо упомянутой фильтрации спецсимволов на стороне сервера, аналогичная фильтрация происходит непрерывно в процессе ввода текста, regexp’ом. При вводе недопустимых символов выводится предупреждение.

Выбор телефона из записной книжки может происходить как drag’n’drop’ом, так и просто кликом на строке (смотря до чего пользователь догадается). Точно также и удаление телефона из списка на отсылку.

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

1.Система уведомлений: При выходе новой версии приложения можно опубликовать новость, которая будет показываться всем пользователем (либо пользователям конкретной соцсети) при очередной загрузке ими приложения.
При этом: а) последующие новости накапливаются и показываются в виде списка, с указанием даты. б) пользователь может нажать “Прочёл”, тогда конкретно этому пользователю именно эта новость больше показываться не будет.

2.Форма обратной связи (во вкладке “Подсказка”). Любое отправленное через неё сообщение записывается  в базу с пометкой, кем именно, когда и из какой соцсети оно отправлено. Соответственно, при желании можно ответить человеку уже через соцсеть. И можно посмотреть все сообщения от этого человека.

Аудитория приложения и статистика

Основная аудитория приложения - школьники из Москвы и Питера, экономящие на оплате за мобильник (либо забывающие за него вовремя заплатить). Причём, женщин почти в полтора раза больше, чем мужчин.

Приходится учитывать, что даже самые, казалось бы, стандартные подходы могут оказаться для этой аудитории неочевидными. Всё должно быть просто и допускать минимум вариантов использования (рекомендую вспомнить IPhone :)
В случае жалоб нужно в первую очередь смотреть на их повторяемость, а не на конкретику. Т.к. в большинстве случаев проблема носит случайный характер (например, у человека ухудшилось соединение с Интернетом, соответственно выдалась ошибка. Или он просто не тот номер телефона ввёл, не ту кнопку нажал и пр). При большом числе пользователей таких случайностей оказывается весьма много.

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

Не так давно, учитывая специфику аудитории, по просьбе пользователей была добавлена проверка грамотности сообщений - через Yandex SpellChecker API (подсвечиваются слова с ошибками, при наведении курсора предлагаются варианты их написания).

Интересно соотношение между числом установок приложения и числом реальных посетителей. Установок около 500 тыс, при этом пользователей, отправивших хотя бы одно сообщение - около 300 тыс. Т.е. получается, что 200 тыс человек приложение поставили, но пользоваться по каким-то причинам [пока] не стали.

Что касается среднесуточного числа посетителей, его видно из второго графика (хорошо видно влияние летних каникул).

Распределение числа отправляемых сообщений в сутках на третьем графике (выборки каждую минуту)

Всего отправлено около 4.5 миллиона сообщений. Входящих сообщений сравнительно немного - 78 тыс. Получили хотя бы один ответ на своё сообщение - около 40 тыс человек.

На самом деле цифры немного больше, т.к. здесь не учтены данные по самой первой версии приложения.

Одно наблюдение: пользовательское соглашение, ссылка на которое находится рядом с кнопкой “Отправить” - смотрят очень многие. Я специально собирал статистику и был несколько удивлён.

Борьба со злоупотреблениями

В конец отправляемого сообщения серверная часть автоматически добавляет имя пользователя (взятое из социальной сети) и название самой сети.

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

Проблема спама также имела место (и сейчас она редко, но ещё встречается). Поскольку в сутки один пользователь может отправить не более 10 бесплатных SMS, люди занимались регистрацией фейковых аккаунтов (причём, явно не вручную) и с них рассылали различный спам. Для борьбы с этим использовался комплекс средств:

- Различные виды банов: по телефону адресата, по тексту сообщения (regexp плюс логика), по ip, по id пользователя. Нецензурные выражения и их производные вырезаются автоматически. Отправка сообщений на короткие номера, а также за пределы РФ - не допускается.

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

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

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

SELECT ip, msgs, ROUND(v) AS v, txt, arr FROM(
   SELECT count(users.ip) AS msgs,users.ip, VAR_SAMP(length(msgs_out.txt)) AS v, MAX(msgs_out.txt) as txt,
   ARRAY_ACCUM(users.user_id) as arr
   FROM msgs_out, users
   WHERE msgs_out.dt_crt > (NOW() - '5 DAY'::INTERVAL)
   AND users.id = msgs_out.user_id_int
   GROUP BY users.ip
   ORDER BY msgs DESC
   LIMIT 50
) AS tmp
WHERE msgs>11 ORDER BY v ASC LIMIT 10


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

- В периоды обострений использовались еще некоторые временные меры, в частности анализировалась активность по отправке любых сообщений с одинаковых ip или id и бан происходил автоматически при достижении некоторого порога (а потом мы уже смотрели, кому не повезло).

Платежи и входящие сообщения

Помимо 10 бесплатных SMS в сутки, можно отправлять и больше, но за внутренние баллы. Эти баллы можно получить двумя способами:

1). Они начисляются, если абонент отвечает на сообщение. Смысл в том, что сообщения на короткий номер, от имени которого приложение посылает SMS - платные (об этом предупреждается). Каждую минуту наличие новых сообщений проверяет скрипт на сервере. Если сообщение появилось, оно заносится в базу, в таблицу входящих, а адресату добавляются баллы.
Клиентское приложение, в свою очередь, регулярно проверяет - не появились ли на сервере новые входящие, а также текущее количество баллов пользователя и отосланных/оставшихся SMS..

2). При переводе вконтактовских голосов на счёт приложения и нажатии “Пополнить”, голоса преобразуются в баллы.

Здесь надо отметить следующее: во-первых, никакие ключевые решения не должны приниматься приложением на стороне клиента. Другими словами, на сервере не должно быть внешних сервисов типа “Добавить N баллов” или “Снять N баллов”. Такие операции должны вызываться лишь в ходе выполнения других (отсылки SMS, например).

И, конечно, во всех API социальных сетей операции с балансом изначально не выполняются по запросу от клиентской части приложения - для этого предусмотрены отдельные серверные методы.

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

Опубликовано: Пётр Соболев

Случайная заметка

9732 дня назад, 00:0030 октября 1998 (Сергей Зефиров, 30 октября 1998) Вступление Слово воксель - voxel - образовано от слова VOlume и аббревиатуры piXEL (pixel, расшифровывается как PICture'S ELement, элемент картины). То есть, переводится как "элемент объемного изображения" или "элемент объема изображения".. Обычно это шар или куб. Не стоит ...далее

Избранное

2970 дней назад, 01:575 мая 2017 Часть 1: От четырёх до восьми Я люблю читать воспоминания людей, заставших первые шаги вычислительной техники в их стране. В них всегда есть какая-то романтика, причём какого она рода — сильно зависит от того, с каких компьютеров люди начали. Обычно это определяется обстоятельствами — местом работы, учёбы, а иногда и вовсе — ...далее

2482 дня назад, 20:305 сентября 2018 "Finally, we come to the instruction we've all been waiting for – SEX!" / из статьи про микропроцессор CDP1802 / В начале 1970-х в США были весьма популярны простые электронные игры типа Pong (в СССР их аналоги появились в продаже через 5-10 лет). Как правило, такие игры не имели микропроцессора и памяти в современном понимании этих слов, а строились на жёсткой ...далее