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

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

3 декабря 2010, 12:55 (5082 дня назад, №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 социальных сетей операции с балансом изначально не выполняются по запросу от клиентской части приложения - для этого предусмотрены отдельные серверные методы.

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

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

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

2284 дня назад, 02:071 августа 2018 Нейронная сеть без электроники. Идея в том, что сначала на компьютере обучается обычная сеть, а потом на 3d принтере создаётся соответствующая ей физическая многослойная структура, через которую свет распространяется в соответствии с функцией, которая в результате обучения получилась. Буквально - с одной стороны на этот ...далее

Избранное

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

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