Доброго дня! Меня зовут Антон, я работаю инженером, в отделе технического обслуживания и администрирования. Языки программирования начал изучать совсем недавно, хотя всегда очень хотелось.
С чего всё началось.
История началась примерно полтора года назад. Блуждая по просторам сети, наткнулся на какой-то курс по программированию (на самом деле достаточно известный), подумал, что давно хотел мигрировать из поддержки в разработчики и записался на пробную часть. Курс был по программированию на Python. Достаточно быстро прошёл вводную часть, научился некоторым азам и понял, что это интересно. Дальше решил уже самостоятельно, с помощью материалов из сети, совершенствовать свои навыки.
Первое что пришло в голову, написать десктопную программу для мониторинга IP-видеокамер. Логика была следующая, если ping до камеры есть, то всё хорошо, если иначе – что-то пошло не так и нужно оповестить об этом специально обученного человека (для оперативности, желательно в Telegram). Задача простая, но полезная для обучающегося программиста. В основу лёг фреймворк PyQt5. Пришлось конечно немного помучатся с мультипроцессингом и зависанием приложения, но итоге приложение было написано и передано в пользование специалисту по видеонаблюдению. Он был рад и пользуется приложением до сих пор. Проект закончен, но хочется дальше развиваться и чем-то помочь коллегам. Вот тут-то и пришла в голову мысль, написать свой Helpdesk.
Проблема и идея
В нашем управлении ИТ работает всего 10 человек – это отдел технического обслуживания, отдел разработки и участок связи. Все, в какой-то степени, занимаются технической поддержкой пользователей. На тот момент все заявки приходили, либо по электронной почте, либо по телефону. Мне показалось что это жутко не удобно и плохо контролируемо:
не было чёткого представления о текущих задачах
о многих заявках можно было попросту забыть
для пользователей не очень понятно в каком состоянии их заявка сейчас
нет никакой очерёдности выполнения, всё по принципу, «кто успел, того и тапки»
нет оперативного уведомления о новых заявках, в следствии чего постоянно разрывающийся телефон, от звонков пользователей
Ранее, у меня уже был опыт использования Helpdesk систем, на базе:
Microsoft SharePoint 2007
OTRS
Первый проработал не долго, в связи с неудобством и отсутствием человека, который бы занимался его поддержкой. Второй, был поднят и настроен мной, но руководство по каким-то причинам, так его и не внедрили. Попытка номер 3, подумал я, и преступил к проектированию, своего helpdesk’a.
Проектирование Helpdesk
Итак, исходные данные:
В организации примерно 500 пользователей, в планах, что все они будут пользоваться helpdesk’ом
10 сотрудников, в 3 отделах, которые также будут использовать helpdesk в работе
Все пользователи делятся на группы, по компаниям и входят в один домен (Active Directory)
Для пользователей системы нужны уведомления по email (для сотрудников ещё в Telegram)
Система будет использоваться только внутри предприятия
Начал с базовой схемы:
То есть сервис, как минимум, должен иметь:
сквозную авторизацию через контроллер домена, форму заявки,
интерфейс пользователя,
интерфейс сотрудника,
уметь уведомлять о новых заявках и о результате их выполнения,
формировать отчёты по заявкам, за определённый период времени, с группировкой по компаниям, отделам, работникам и т.д. И выгружать это, например, в Excel.
Так как я начал изучать Python, было решено делать это всё на Django. Первым делом начал искать и изучать подобные системы, и что они в себя включают. Получилась примерно такая структура:
Вперёд к знаниям
Согласно схеме БД:
Пользователь имеет основной профиль и дополнительный
Каждая заявка привязана к пользователю
К заявке можно оставлять комментарии
К заявке можно прикрепить файл
Каждый пользователь имеет доступ к уведомлениям о своих заявках
На основе этого, были созданы соответствующие модели и проведены миграции.
Авторизация
Так как все пользователи на предприятии работают на компьютерах, находящихся в домене и авторизуются на компьютерах с помощью Active Directory, то и на helpdesk’e авторизация должна быть через Active Directory. Причём это должно происходить без участия пользователя. То есть, если пользователь вошёл на компьютер под своей учётной записью, то при открытии helpdesk’a он автоматически авторизуется.
Начитавший информации в интернете приступил за дело. Для такого функционала мне понадобился:
Nginx, собранный с модулем SPNEGO
дополнительные библиотеки для Django – django-auth-ldap и django-remote-auth-ldap
Ещё хотелось, чтобы информация о пользователях хранящаяся в Active Directory (компания, подразделение, телефон и т.д.) автоматически подтягивалась в профиль пользователя. К счастью это, достаточно просто получилось реализовать с помощью тех же средств и сигналов Django.
Как это всё настроить есть информация в Интернете. У меня это работает так:
Пользователь заходит на сайт
Nginx авторизует его, и передает параметр REMOTE_USER в Django
Если такого пользователя ещё нет, Django, с помощью django-auth-ldap, создаёт новую учётную запись. Если есть, проверяет соответствие данных в учетной записи и в Active Directory и обновляет их.
Как итог, пользователи попадают на helpdesk уже авторизованными.
Создание заявки
Следующим этапом было, непосредственно само создание заявки. Начал с создания формы заявки. Она должна быть достаточно простая и легко понимаемая для пользователя. Остановился на такой форме заявки:
Заявитель, подразделение и телефон автоматически подтягиваются из профиля пользователя (если там есть такая информация). Пользователю остаётся лишь выбрать категорию, добавить описание, если нужно, приложить файлы. По моему мнению, получилось достаточно просто и понятно.
Список всех заявок
Список всех заявок решил сделать в виде таблицы с некоторыми фильтрами.
Так выглядит интерфейс всех заявок в кабинете пользователя. Пользователь видит только заявки, созданные непосредственно им.
В кабинете сотрудника, таблица выглядит немного иначе, чуть больше информации.
Сотрудники видят только те заявки, которые предназначены их отделу. Это значит, что, сотрудник из отдела разработки увидит только заявки, отправленные в категорию «Отдел разработки».
Так же есть группа для руководителей, они видят заявки всех категорий. И если пользователь, случайно отправил заявку не в тот отдел, руководитель может её скорректировать.
Редактирование заявки
Редактирование заявки было решено выводить с помощью модального окна, в виде обычных форм.
Для сотрудников и руководителей форма более содержательная и с большими правами на редактирование:
Уведомления
При создании (изменении) заявки необходимо было отправлять уведомления, сотрудникам соответствующих отделов и пользователям, на электронную почту. В зависимости от категории заявки, уведомление отправляется только определённой группе сотрудников.
Для уведомления по эл. почте использовалась стандартная библиотека send_mail. Иногда происходило подвисание интерфейса, при каких-либо действиях с заявкой. Пришлось покопаться и как позже выяснилось, это происходит из-за долгого ответа почтового сервера. Пришлось искать выход, и он был найден в лице Celery. Настроил Celery таким образом, чтобы он создавал таски на отправку и формировал очередь. Это позволило не подвисать интерфейсу при долгом ответе почтового сервера и работать более стабильно.
Для большей оперативности, для сотрудников было реализовано уведомление в Telegram с помощью библиотеки pyTelegramBotAPI. Пример уведомления ниже:
Как только происходят изменения c заявкой, backend даёт команду боту отправить уведомление в соответствующую отделу группу в Telegram. Группы в Telegram для соответствующих отделов, были созданы предварительно.
Отчёты по заявкам
Для отчётов по заявкам была выделена отдельная страница с формой:
После выбора параметров генерируется отчёт, который можно скачать в формате Excel. Доступ к отчётам доступен только руководителям подразделений.
Введение в работу
По завершению работы, насколько смог протестировал систему. Коллегам тоже пришлось поработать в качестве тестировщиков. Все баги, которые нашли, я как порядочный разработчик старался исправить.
С помощью групповых политик раскидал ярлык Helpdesk’a на рабочие столы пользователей и написал инструкцию «Как пользоваться Helpdesk’ом»
Старт Helpdesk’a был назначен. В назначенный день всем по электронной почте разослали инструкцию. С тех пор система заявок работает. Сначала пользователи по привычке пытались оставлять заявки по телефону и по электронной почте, но со временем почти все привыкли и стали использовать Helpdesk.
Заключение
На этом первая часть Pet-проекта для обучения, была закончена. Система Helpdesk получилась вполне рабочая, люди ей пользуются, многим это даже нравится. Я же немного продвинулся в сторону разработки и это радует.
Пишу в первый раз, поэтому не судите строго.
P.S. Спустя почти год после запуска уже многое было переделано, изменено и добавлено. Сейчас используется уже такой стек технологий:
Nginx - в роли обратного прокси сервера, с SSO авторизацией в Active Directory,
Django (DRF) – backend с LDAP авторизацией,
Django Channels – для работы с WebSocket,
Celery - для очереди заданий,
Redis,
PostgreSQL,
React (React Redux) – в качестве frontend’a,
Docker (Docker-compose) - для сборки всего вышеперечисленного.
Комментарии (22)
2FED
06.07.2022 20:38+2Странное решение - запрещать пользователям оставлять заявки через почту. Ведь можно выделить для хелпдеска отдельный ящик, и настроить парсинг писем из этого ящика в хелпдеск.
Люди гораздо охотнее пользуются сервисами, которые заставляют минимально корректировать их жизненные привычки. Да, это добавляет необходимость операции классификации заявки для назначения на нужную группу, но для процесса гораздо важнее получить заявку в систему пусть даже в не полностью готовом виде, чем провоцировать пользователя использовать не регламентированные каналы связи с техническим департаментом.
На самом деле даже в приеме звонков по телефону нет ничего плохого (ведь пользователь не может попасть в хелпдеск если у него не включается ПК или перестало работать сетевое соединение) - просто необходимо выделить одного оператора, который должен заниматься только регистрацией и классификацией заявок. Да, это накладные расходы, но это значительно повышает приживаемость системы в эксплуатации: сервис деск это в первую очередь инструмент повышения качества обслуживания для IT отдела. Доя пользователя его выгода не очевидна, и чем меньше он контактирует с системой, тем ему удобнее.
Я понимаю, что статья не столько про сервсис-деск, сколько про факт использования новых навыков, но проектирование бизнес-процессов моя профессиональная область и не смог пройти мимо очевидных ошибок новичка)
Вообще, если планируете развивать свой сервис, рекомендую почитать/посмотреть про процесс управления инцидентами в библиотеке ITIL(желательно версии 3 или 4). Хотя бы беглое знакомство с ним позволит избежать детских ошибок в покорении вершин становления хелпдеска.
Правильно организованный хелпдеск, при должной квалификации инженеров позволяет вдесятером обслуживать компанию размером в 2-3 тысячи пользователей.
Oltrs Автор
06.07.2022 20:56Спасибо за наводки, обязательно почитаю. Да, действительно хэлпдеск делался для облегчения жизни пользователей и сотрудников в том числе. Заявки по телефону и электронной почте тоже регистрируются, но пока что в ручную, сотрудниками. В будущем надеюсь, автоматизировать заявки из почты.
danilovmy
06.07.2022 23:17+1а можно посмотреть код проекта?
Oltrs Автор
07.07.2022 07:40К сожалению, код проекта в том виде в котором описан в статье не сохранился. Многое уже переделано.
danilovmy
07.07.2022 09:55Для ознакомления подойдут, например, ранние коммиты. А, в принципе, особой разницы то нет, современную версию тоже интересно увидеть. Всегда полезно знать, как такой вопрос другие разработчики решают на Django.
Я бы все же переопределил модель пользователя, для работы с одной моделью пользователя а не двумя. Ну и тикет и коммент это же одна и та же модель через foreign(self). у меня это сделано так.
Oltrs Автор
07.07.2022 13:34Ранних коммитов не было, всё делалось локально. Возможно напишу статью про то что есть сейчас, с кодом.
Модель пользователя - таблица Account расширяет стандартную модель Django User через поле OneToOneField.
Ticket и Comment это две разные таблицы. Comment связан и с Ticket, и с User, через ForeignKey. То есть у одной заявки может быть много комментариев от разных пользователей. У меня это сделано так.
danilovmy
07.07.2022 14:45Я имел ввиду, что Можно поменять модель пользователя и избавиться от двух таблиц и одной o2o.
По поводу таблиц "тикет-комментарий" смотри: поля дублируются, было б неплохо в комментарии тоже иметь дату и т.п. Если сделать только одну таблицу "тикеты" с parent=ForeignKey('self'), то, в итоге, любой комментарий - это тот же тикет, правда с заполненным полем "родитель". При этой структуре родитель-ребенок можно комментировать не только тикеты но и комментарии, которые тоже суть есть тикеты. Юзер у каждого тикета(комментария) свой. Кроме масштабируемой, хоть и рекурсивной структуры, становится проще поиск.
Oltrs Автор
07.07.2022 15:18Понял что вы имеете в виду.
По поводу модели пользователя, дополнительные поля были добавлены позже, поэтому было решено делать так как проще, то есть создать ещё одну таблицу и соответственно связь o2o. В следующих проектах, конечно же буду сразу делать кастомную модель пользователя.
По поводу таблиц "тикет-комментарий", даже не подумал что можно сделать так как вы предлагаете. Для меня логичнее было создать отдельную таблицу, но ваш вариант, тоже вариант )
kit_oz
08.07.2022 07:18Пожалуйста, не надо так.
Тикет и комментарий - это разные сущности, и жить они должны отдельно. Даже не смотря на часть"похожих" полей, которые, на самом деле, описывают разные вещи.
danilovmy
08.07.2022 15:01В Django ticket и comment организуются через proxy модели, потому это разные сущности, со своими методами.
Хотя вопрос остается. @kit_oz А чем таким они разные, если они просто частички обсуждения одной темы?
kit_oz
09.07.2022 12:56Тикет - объект, обозначающий некий запрос от потребителя к исполнителю: сломался компьютер, создайте учётку. Каждый объект - самостоятельная сущность, не нуждающаяся ни в чём извне.
Комментарий - просто какой-то текст, относящийся к тому, к чему данный комментарий прикреплён, будь то тикет, чат или статья на Хабре, которую мы комментирует (представляете, Хабр хранил бы комментарии в одной таблице со статьями?). Без главного документа комментарий не имеет вообще никакой ценности.
Использование прокси модели несомненно гораздо лучше прямого переиспользования одной модели, но всё равно несёт свои недостатки:
Замедление обращения к тикетам, из-за раздутия таблицы строками с комментариями
В случае с некоторыми бд - излишнее резервирование места под незаполненные поля (а в случае с двумя таблицами в одной - по-любому куча полей используется строго только в одной модели из двух)
А что делать, если вы захотели для комментария изменить поле, но это поле используется и в тикете, и там эта правка всё сломает?
То, о чём Вы говорили изначально, идеально выносится в абстрактные модели джанги - код лежит в одном месте и управляется централизованно, но в базе данных для каждой модели создаётся отдельная таблица, с нужными только данной модели реквизитами.
DanyByLuckyCraft
07.07.2022 04:25+2Не хочу придираться, но в диаграмме связи не правильно проставлены.
Туть подробно, на мой взгляд, описаны все нюансы по проектированию диаграмм БД.
Oltrs Автор
07.07.2022 07:44Да, точно, связи не правильно проставлены. Спасибо за ссылку, там действительно исчерпывающе описано.
EPIDEMIASH
07.07.2022 12:17А что у вас по SLA? Вы же никак это не контролируете в своей системе.
Oltrs Автор
07.07.2022 13:18Да, пока что в данной системе это никак не контролируется. Это всё таки Pet-проект, которым я занимаюсь один, в свободное время. У меня много всяческих идей по поводу развития сервиса, но не всегда есть время и знания, что бы их воплотить в жизнь. В будущем это конечно же планируется добавить SLA, для большей осведомлённости пользователей и повышения качества сервиса.
LeshaRB
Хранение файлов в БД так себе решение...
А зачем уж так сильно дробить таблицу Ticket, выносить в отдельные таблицы справочники информацию?
aegoroff
На самом деле там Таблица статусов выглядит лишней (можно enum применить), да между Task_tiket надо вставить таблицу развязку (связь многие ко многим), а остальное нормально.
Про файлы в БД - в данном случае это выглядит удобнее, нежели чем возня с файловой системой (головная боль от бакапов, протухших ссылок на файлы и пр.), разумеется размеры файлов надо ограничивать.
Oltrs Автор
По первому вопросу, в базе хранятся только ссылки на файлы, сами файлы загружаются в папку media/numberTicket
По поводу отдельных справочников, мне показалось это достаточно разумно, вынести в отдельные таблицы информацию, которая не меняется от тикета к тикету, а просто берется из справочников. И как мне кажется, так проще и удобней делать выборку. Буду рад услышать как это сделать правильней.
DanyByLuckyCraft
Все правильно. Одно из важнейших правил нормализации, все что дублируется и можно объединить в некий "справочник" - должно быть в отдельной таблице.
sentike
Я бы посоветовал использовать minio для хранения файлов, если хочется иметь автономию и не прибивать себя к azure / amazon. Таким образом ты получишь надёжное распределенной хранилище. Доступ к нему можно сделать публичным при необходимости