Search
Write a publication
Pull to refresh

О том, как я пытался подружить систему заявок с клиентским информером.

Привет, %username%!

Этот пост я посвящаю в первую очередь своим друзьям, а так же тем, кто считают фреймворки верхом совершенства, а все остальные велосипеды — маст дай.
Столкнувшись с чем то новым я предпочитаю написать велосипед, чтобы понять, как оно работает, чтобы потом, например, при использовании ajax-фреймворка не говорить, что загрузка файлов происходит в формате xml, и поэтому ajax-upload не рулит!

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

Схема работы:
Сейчас это работает так: потенциальный клиент заходит на сайт, заполняет форму, и содержимое формы уходит на емаил кассиру. Кассир проверяет(иногда) почту, ну и если есть заявки, то что то с ними делает. Шеф захотел как то повысить производительность труда, одновременно облегчить жизнь кассирам, что и поручил мне. Кроме того, в перспективе была интеграция с платежными терминалами…

И тут разгулялась моя фантазия:
Поднял сервер, на нем крутится база данных, и вебморда, написанная на пхп. В базе данных хранятся все заявки от клиентов, а обработка и осуществляется через вебморду.
Кроме того, чтобы девочки постоянно не проверяли почту, написал на своем любимом C# небольшую программку, которая периодически проверяет заявки, и если таковые есть, показывает красиво моргающее окошко с уведомлением.

Осталось придумать, как клиентскую программку, назовем ее информером взаимодействовать с БД? Прямой доступ к БД давать не хотелось, поэтому я снова подгрузил модуль Fantasy.dll

Первое что пришло на ум — написать скрипт, который бы выдавал информеру циферку, которая характеризует количество необработанных заявок, но такой вариант мне показался слишком простым. И тут я вспомнил о веб-сервисах — о всяких там xml-rpc, soap, etc… Посовещавшись с другом решил взяться за использование soap. Ах, как же я был наивен, что с ним все будет так просто :) Для использования SOAP оказалось нужно генерить какой то хитрый xml c описанием функций, которые предоставляет вебсервис. как этот WSDL генерить я тогда еще не знал, а руками писать не хотелось. Спустя некоторое время эту функцию я нашел в IDE, которую использую для разработки, но к тому времени я решил отказаться от SOAP, т.к. мне не нравилось количество мусорного трафика, которым эти ваши soap-вебсервисы сопровождались. Кроме того, поговорить с другим знакомым, который был знаком с технарями из местной платежной системы, сказал что с соапом меня там пошлют куда подальше :(

Я придумал свой вариант:
написал скрипт, которому первым параметром передается команда, вторым параметром передаются аргументы к этой команде(блин, как же их передать? :() ну и третий аргумент — ключ, по которому скрипт идентифицирует того, кто выполняет конкретную команду, а если не идентифицирует — говорит что «сюда низя, здесь кролики!»

Как передавать аргументы:
Сразу же подумал об XML.
Когда гуглил про SOAP, наткнулся в википедии на клевую аббревиатуру WDDX. Погуглил еще немножко, и нашел класс написанный на C#, который сможет обеспечить требуемый функционал для информера. Принялся за работу. Написал на пхп пебольшую обертку над wddx, чтоб красивее использовать:
class Wddx
{
function MakePacket($value)
{
return @wddx_serialize_value($value);
}

function ParsePacket($packet)
{
return @wddx_unserialize($packet);
}
}


Данные
Данные в базе хранятся в кодировке utf-8, соединение с БД пхп скрипт поддерживает в кодировке Utf-8, вебморда клиентам отдается в utf-8, скрипты хранятся в кодировке utf-8.

Написал обработчик команды get_new_tickets_count, которая понятно дело, возвращает количество новых не обработанных заявок информеру. Все работает, супер! Уже был готов показать шефу как красиво моргает окошко с уведомлением, и чтобы совсем было красиво, фамилию, имя кассира, хранящиеся в БД из транслита решил написать русскими буквами. Открыл пхпмайадмин, заменил значение соответствующего поля, запустил информер, а он мне показывает что-то, но что именно — непонятно. Открыл в браузере этот урл, вроде бы все правильно, страничка отдается в юникоде, но данные почему то портятся. Начал искать проблему. Проблема оказалась в wddx. функции этого расширения зачем-то меняют кодировку, и соответственно, это дело отдается уже и не в UTF-8. Решение искал довольно долго, в итоге нашел — помогла волшебная функция iconv(«UTF-8», «Волшебная кодировка, название которой забыто на веки», xml_пакет_который_делает_функция_wddx_serialize_value).
Работает! Круто! Но я не понял, почему страница отдается в юникоде, а текст из юникода кодируется в какую то загадочную кодировку, чтобы корректно отображаться в окне браузера. Ну да фиг с ним.

Запускаю информер, и вижу «Имя кассира: ВсЯкИеТаМиЕрОгЛиФы».

Как информер пытался узнать имя кассира:
Имя кассира узнавалось при старте программы
// В конструкторе формы было написано
WebDownloader = new WebClient();
d = new WddxDeserializer();
this.GetManagerName(SystemConfig.ManagerId); // SystemConfig.ManagerId - персональный ключ, который присваивается каждой учетной записи в системе

// дальше в коде программы
private void GetManagerName(string hash)
{
try
{
string dataPacket = WebDownloader.DownloadString(SystemConfig.TicketApiUrl + "net_get_manager_name&manager=" + SystemConfig.ManagerId); // формуруем УРЛ и посылаем запрос, в результате которого хотим получить содержимое wddx пакета
this.ServiceState = ConnectionState.ServiceAvailable; // Переключатель, которым регулируется поведение программы
Hashtable resultTable = (HashTable)d.Deserialize(dataPacket); // десериализируем
if (resultTable["error"] != null) // если есть ошибка, в трее будет написано соответствующее уведомление
{
TrayIco.Text = "Не удалось получить имя кассира\nКод: " + resultTable["error"].ToString();
}
else // иначе выведем имя кассира
{
SystemConfig.ManagerName = resultTable["manager_name"].ToString();
TrayIco.Text = "Кассир: " + resultTable["manager_name"].ToString();
}
}
catch { }
}


Из за своей невнимательности проблему иероглифов я заметил не сразу, А из за того, что пхпшный wddx оказался каким то странным я решил изобрести очередной велосипед. Велосипед по типу wddx, и полностью своей реализации. Передавать в скрипт требовалось простые структуры по типу одномерных ассоциативных массивов (ключ = значение). В итоге пришел к такому виду пакета:
/>
/>
/>



За 5 минут написал пхп парсер этого дела:
class DataPacket
{
public static $data;

public static function ParseXmlString($xmlstr)
{
$doc = new DOMDocument();
@$doc->loadXML($xmlstr);

$xpath = new DOMXPath($doc);
$nodes = @$xpath->query("/DataPacket/Param");

foreach($nodes as $node)
{
self::$data[@$node->getAttribute('Key')] = @$node->getAttribute('Value');
}

return self::$data;
}

public function AddData($key, $value)
{
$this->data[$key] = $value;
return $this;
}

public function Clear()
{
$this->data = null;
}

public function GenerateXml()
{
$str = '';
foreach($this->data as $k => $v)
{
$str .= '/>';
}
$str .= "";
return $str;
}
}


Это позволило на выходе получать обычный ассоциативный массив на выходе при передаче не менее обычного xml на входе:
$packet = DataPacket::ParseXmlString($xmlstring);


Что-ж, наш «веб-сервис» умеет генерировать xml в своем фирменном формате, осталось немного — научить информер парсить этот несложный xml. Про тип данных Hashtable в C# я узнал совсем недавно — как раз тогда, когда прикручивал WddxDeserializer :) Кода в информере очень мало, но кардинально менять его не хотелось, и я решил написать аналог метода d.Deserialize():
private Hashtable ParseResponse(string xml)
{
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(xml);
XmlNodeList nodes = xdoc.SelectNodes("/DataPacket/Param"); // xpath прекрасен ;)
Hashtable table = new Hashtable();
foreach (XmlNode node in nodes)
{
table.Add(node.Attributes["Key"].Value, node.Attributes["Value"].Value);
}
return table;
}


В методе GetManagerName меняем
Hashtable resultTable = (HashTable)d.Deserialize(dataPacket);

на
Hashtable resultTable = ParseResponse(dataPacket);


Компилим, запускаем, и на выходе у нас опять все те же иероглифы. И только после 15 минут передышки и спокойных раздумий до меня дошло, что я не знаю, в какой кодировке мой WebDownloader принимает ответ скрипта. Иду в конструктор формы и добавляю следующий код:
WebDownloader.Encoding = Encoding.UTF8;

Этим я сообщил даунлоадеру что данные нужно принимать в юникоде.

Компилю, запускаю — It's works!..

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

Надо сказать, что из этого я сделал несколько полезных выводов, и это послужило мне хорошим уроком => XP++ :)
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.