Конфигурация ЛК состоит из трех файлов, расположенных в папке WEB-INF: основного файла конфигурации mybgbilling-conf.groovy, файла конфигурации меню mybgbilling-menu.groovy, файла конфигурации платежных систем mybgbilling-payment.groovy. Файлы конфигурации созданы с использованием синтаксиса Groovy.
Описание синтаксиса
Группы параметров в конфиге разделяются не точкой, а с помощью вложенных блоков. Значение параметра должно быть правильным Groovy/Java-объектом - в простом случае строкой, заключенной в одинарные или двойные кавычки, или числом, например:
one { two { parameterA = 'value1' three { parameterA = 'value2' parameterB = 100 } } }
Т.е. параметр конфигурации - это один или несколько вложенных блоков, имя параметра и значение после знака =. Данный пример в конфигурации модулей биллинга выглядел бы так:
one.two.parameterA=value1 one.two.three.parameterA=value2 one.two.three.parameterB=100
Некоторые значения параметров должны быть списками или массивами определенных объектов. Объекты списка заключены в квадратные скобки [] и разделены между собой символом запятой. Например:
authentication { modes = [ authenticationMode { mode = 'contract' }, authenticationMode { mode = 'login' module = 'inet' moduleId = 1 } ] }
Некоторые значения параметров могут быть ассоциативными массивами (список ключ:значение, map). Связки ключ:значение заключены в квадратные скобки [] и разделены между собой символом запятой. Например:
example { map = [ key: 'value', key2: 200 ] }
Также параметры могут быть прописаны как ассоциативный массив, заключенный в круглые скобки (значение в этом случае прописывается через ':' (двеоточие), а не через символ '='):
authentication { modes = [ authenticationMode( mode: 'contract' ), authenticationMode( mode: 'login', module: 'inet', moduleId: 1 ) } ] }
Некоторые значения могут быть динамическими, если использовать замыкания (closure). Т.е., грубо говоря, значением может быть функция, которая будет возвращать нужное значение:
status { // возможность изменения статуса договора //statusChange = { contract -> return contractInGroup( contract, [1, 2, 3, 4, 20] ) && isCustomer(); } //statusChange = { contract -> contractInGroup( contract, [1, 2, 3, 4, 20] ) && isCustomer() } statusChange = { isCustomer() } }
В mybgbilling-conf.groovy и mybgbilling-menu.groovy в таких замыканиях можно использовать определенный набор методов, аргумент объект-contract, а также дополнительные аргументы, специфичные для определенного параметра конфигурации (например, параметры content.kernel.customerTitle и content.kernel.subContractGroup):
content { kernel { // название контрагента, отображаемое на странице customerTitle = { contract, contractParameterMap -> // ID параметров договоров названия физ. лиц (для customerTitle) def individualCustomerTitleParamIds = [0, 0, 0, 0, 0]; // ID параметров договоров названия юр. лиц (для customerTitle) def corporationCustomerParamIds = [0, 0, 0, 0, 0]; def paramIds = contract.personType == 1 ? corporationCustomerParamIds : individualCustomerTitleParamIds; String result = contractParameterMap.values().stream() .filter{ v -> paramIds.contains( v.entitySpecAttrId ) && notBlankString( v.toString() ) } .findFirst() .map{ v -> v.toString() } .orElse( null ); // можно отобразить и просто комментарий договора //if( result == null ) { // result = contract.comment; //} return result; } } }
В замыканиях можно использовать методы:
- isCustomer() или isUserInRole('customer') - возвращает true, если в режиме аутентификации, которым воспользовался абонент, не указан параметр role = 'unauthCustomer';
- contractInGroup( contract, groupIds ) - возвращает true, если переданный в первый аргумент объект-contract содержит в себе одну из групп, указанных списке второго аргумента, например: contractInGroup( contract, [2, 3, 8, 13] ).
Основная конфигурация (mybgbilling-conf.groovy)
Основная конфигурация личного кабинета состоит из нескольких блоков:
- bgbilling - конфигурация подключения к BGBillingServer,
- authentication - параметры аутентификации абонента,
- mail - параметры почтовой подсистемы (чтобы ЛК мог отправлять письма при необходимости),
- content - параметры содержимого страниц.
Конфигурация подключения к BGBillingServer
// Параметры подключения к BGBillingServer. // ЛК является пользователем биллинга, общается с ним также, как BGBillingClient bgbilling { // URL доступа к BGBilling url = 'http://127.0.0.1:8080/bgbilling/executer' // Логин user = 'customer' // Пароль password = '123456' }
Параметры идентификации HTTP-соединения
Личному кабинету в некоторых случаях требуется знать базовый URL, по которому абоненты получают доступ к нему. Личный кабинет биллинга может получить это значение из запроса, однако при использовании NGINX значение из запроса может быть не правильным. Поэтому базовый URL следует указать в конфигурации в параметре baseUrl.
Также личному кабинету требуется знать IP-адрес абонента, который пользуется им в текущий момент (например, для авторизации по IP-адресу или блокировке при переборе логинов/паролей). Поэтому при использовании NGINX требуется указать HTTP-заголовок в параметре context.hostHttpRequestHeader, из которого получать реальный IP-адрес вместо физического IP-адреса HTTP-соединения.
context { // Базовый адрес сервера (через который абоненты получают доступ к ЛК). По умолчанию используется значение из запроса //baseUrl = 'https://provider.ru/selfcare' baseUrl = 'https://my.provider.ru' // Идентификатор хоста по HTTP-заголовку, например, X-Real-IP. По умолчанию используется IP-адрес хоста hostHttpRequestHeader = 'X-Real-IP' }
Параметры аутентификации абонента
// Параметры аутентификации абонента authentication { // Кол-во ошибок аутентификации, после которого будет отображаться captcha для этого логина captchaLoginErrorCount = 5 // Кол-во ошибок аутентификации, после которого будет отображаться captcha для хоста captchaHostErrorCount = 20 // Кол-во ошибок аутентификации, после которых будут заблокированы попытки этого хоста blockHostErrorCount = 30 // Режимы аутентификации для входа в ЛК modes = [ // аутентификация по номеру договора authenticationMode { mode = 'contract' } ] }
Режимов аутентификации может быть несколько - в этом случае в окне логина можно выбрать необходимый. На данный момент поддерживаются 6 режимов аутентификации:
по номеру договора:
authenticationMode { mode = 'contract' }
по логину модуля Inet:
authenticationMode { module = 'inet' mode = 'login' // ID модуля moduleId = 1 }
по IP-адресу сессии модуля Inet (вход без пароля):
authenticationMode { module = 'inet' mode = 'ip' // ID модуля moduleId = 1 // ограниченный доступ role = 'unauthCustomer' }
по параметру договора "телефон":
authenticationMode { module = 'kernel' mode = 'phoneParam' // ID параметра parameterId = 1 // преобразование введенного номера договора username = { s -> s.replaceAll( /^8(.+)$/,'7$1' ) } }
по параметру договора "Email":
authenticationMode { module = 'kernel' mode = 'emailParam' // ID параметра parameterId = 2 // преобразование введенного номера телефона username = { s -> s.replaceAll( /^8(.+)$/,'7$1' ) } }
по текстовому параметру договора:
authenticationMode { module = 'kernel' mode = 'textParam' // ID параметра parameterId = 3 }
При аутентификации по параметру договора в качестве пароля используется пароль к личному кабинету (как и при аутентификации номеру договора). При аутентификации по номеру телефона идет поиск только по введенным цифрам, при этом можно задать преобразование введенного номера в другой вид с помощью параметра username; если параметр username не указан, то по умолчанию 8 в начале строки заменяется на 7.
Для режима аутентификации можно назначить, чтобы доступ после аутентификации через него был ограничен. Для этого указывается параметр role = 'unauthCustomer'. В этом случае, вызов isUserInRole( "customer" ) будет возвращать false. Ограниченный доступ может быть указан, например, для режима аутентификации по IP-адресу модуля Inet.
Можно разрешить аутентификацию только для определенных групп договоров, указав условие в параметре filter:
authenticationMode { module = 'inet' mode = 'ip' //ID модуля moduleId = 1 // ограниченный доступ role = 'unauthCustomer' // фильтр по группам договоров filter = { contract -> contractInGroup( contract, [1, 2, 3, 4, 20] ) } }
Или наоборот, запретить для определенных групп договоров:
filter = { contract -> !contractInGroup( contract, [1, 2, 3, 4, 20] ) }
Или разрешить по номеру договора:
filter = { contract -> contract.title.startsWith( "NK" ) }
Или использовать регулярное выражение:
filter = { contract -> contract.title.matches( "NK.*" ) }
Параметры почтовой подсистемы
// Параметры SMTP, чтобы ЛК мог отправлять письма mail { smtp { host = 'smtp.provider.ru' } from { email = 'support@provider.ru' name = 'BGBilling' } }
Параметры содержимого страниц
Разрешенные фрагменты
Данный блок конфигурации позволяет настраивать, какие фрагменты страницы или какие действия доступны абонентам или группам абонентов. Например, в коде страницы статусов договора есть фрагмент смены статуса:
<ui:fragment rendered="#{configuration.get('content.kernel.status.statusChange', true)}"> ... </ui:fragment>
Соответственно можно в конфигурации запретить всем менять статус договора из личного кабинета:
content { kernel { ... // status.xhtml status { // возможность изменения статуса договора statusChange = false } ... } ... }
Можно разрешить только тем, кто был аутентифицирован по логину/паролю (в конфигурации по умолчанию установлен этот вариант):
statusChange = { isUserInRole( "customer" ) }
Разрешить только аутентифицированным по логину/паролю физ. лицам:
statusChange = { contract -> isUserInRole( "customer" ) && contract.getPersonType() == 0 }
Или разрешить только аутентифицированным по логину/паролю определенным группам договоров:
statusChange = { contract -> isUserInRole( "customer" ) && contractInGroup( contract, [1, 2, 3, 4, 20] ) }
Название контрагента в верхней части страницы
По умолчанию в шапке страницы название или имя контрагента не отображается. За отображение названия (или имени) отвечает параметр content.kernel.customerTitle. В конфигурации можно указать, чтобы отображался комментарий договора:
content { kernel { // название контрагента, отображаемое на странице customerTitle = { contract, contractParameterMap -> contract.comment } ... }
Или же отобразить параметр договора, в зависимости от типа лица договора (физ. лицо или юр. лицо):
content { kernel { // название контрагента, отображаемое на странице customerTitle = { contract, contractParameterMap -> // ID параметров договоров названия физ. лиц (для customerTitle) def individualCustomerTitleParamIds = [33, 0, 0, 0, 0]; // ID параметров договоров названия юр. лиц (для customerTitle) def corporationCustomerParamIds = [10, 0, 0, 0, 0]; def paramIds = contract.personType == 1 ? corporationCustomerParamIds : individualCustomerTitleParamIds; String result = contractParameterMap.values().stream() .filter{ v -> paramIds.contains( v.entitySpecAttrId ) && notBlankString( v.toString() ) } .findFirst() .map{ v -> v.toString() } .orElse( null ); return result; } ... } ... }
Группировка субдоговоров в меню
Если субдоговоров у данного договора меньше 10 - они отображаются прямо в меню. В этом случае можно сортировать и группировать список субдоговоров:
content { kernel { // группировка субдоговоров (для меню) subContractGroup = { subContractList -> subContractList .stream() .sorted({ a,b -> a.title.compareTo(b.title) }) .collect( Collectors.groupingBy{ contract -> // можно группировать субдоговора по группам договоров if( contractInGroup( contract, [1, 2, 3, 4, 20] ) ) { return "contract.sub.group.01.internet"; }else if( contractInGroup( contract, [5, 6, 7, 8, 9] ) ) { return "contract.sub.group.02.phone"; } else { return "contract.sub.group.99.other"; } // если всем возвращать пустую строку - то группировки не будет return ""; }) .entrySet() .stream() .sorted({ a,b -> a.key.compareTo(b.key) }) .collect( Collectors.toList() ); } ... } ... }
В примере при группировке используются строки вида "contract.sub.group.01.internet". Число в данном случае используется для сортировки групп, а само название группы должно быть прописано в Locale_ru_RU.properties по ключу:
contract.sub.group.01.internet=Интернет contract.sub.group.02.phone=Телефония contract.sub.group.99.other=Другое
Конфигурация меню (mybgbilling-menu.groovy)
Данный файл конфигурации возвращает дерево пунктов меню для договора. Выглядит конфигурация, например, так:
menu { // список пунктов верхнего уровня children = [ // Новости menu( page: "kernel/news", icon: "fa-newspaper-o", title: "menu.news" ), // Уведомления + Рассылки menu( page: "kernel/notificationsEx", subPage: "notifications", icon: "fa-envelope-o", title: "menu.notifications", badge: "#{notificationBean.getUnreadCount()}", badgeUpdate: "#{notificationBean.populate()}", show: isCustomer() ), // Уведомления (отдельно от рассылок) menu( page: "kernel/notifications", icon: "fa-envelope-o", title: "menu.notifications", show: !isCustomer() ), // Баланс menu( page: "kernel/balance", icon: "fa-rub", title: "menu.balance" ), // Лимит menu( page: "kernel/limit", icon: "fa-umbrella", title: "menu.limit" ), // Тарифные опции menu( page: "kernel/tariffOptions", icon: "fa-cogs", title: "menu.tariffOptions", show: isCustomer() ), // Договор menu( icon: "fa-briefcase", title: "menu.contract" ) { children = [ // Статус menu( page: "kernel/status", title: "menu.status" ), // Тарифы menu( page: "kernel/tariffs", title: "menu.tariffs", show: isCustomer() ), // Действия menu( page: "kernel/additionalActions", title: "menu.additionalActions", show: isCustomer() ), // Документы menu( page: "kernel/documents", title: "menu.documents", show: isCustomer() ), // Документы (включены в предыдущий пункт) //menu( page: "plugins/documents/documents", title: "menu.documents" ), // Бухгалтерия menu( module: "bill", page: "modules/bill/bill", title: "menu.bill", show: isCustomer() ), // Примечания menu( page: "kernel/notes", title: "menu.notes", show: isCustomer() ), // Смена пароля menu( page: "kernel/password", title: "menu.password", show: isCustomer() ) ] }, // Интернет menu( module:"inet", icon:"fa-globe", title:"menu.inet" ) { children = [ // Сессии menu( page: "modules/inet/sessions", title: "menu.inet.sessions" ), // Трафик menu( page: "modules/inet/traffics", title: "menu.inet.traffics" ), // Смена пароля menu( page: "modules/inet/password", title: "menu.inet.password", show: isCustomer() ) ] }, // ТВ menu( module:"tv", page:"modules/tv/tv", icon:"fa-tv", title:"menu.tv" ), // Поддержка menu( page: "plugins/helpdesk/helpdesk", icon: "fa-wrench", title: "menu.helpdesk", badge: "#{helpdeskBean.getUnreadTopicCount()}", badgeUpdate: "#{helpdeskBean.populateTopics()}", show: isCustomer() ) ] }
У каждого объекта-пункта меню есть набор параметров:
- module - модуль, если данный пункт относится к модулю, наследуется дочерними пунктами;
- moduleId - ID модуля (необязательно, если указан module, то подставляется автоматически), наследуется дочерними пунктами. Можно использовать, если одинаковые модули нужно показывать по разному;
- page - страница, без .xhtml;
- subPage - подстраница;
- icon - иконка;
- title - название пункта меню (ключ для Locale.properties);
- badge - счетчик, указывается JSF-вызов метода, который вернет число;
- badgeUpdate - JSF-вызов метода, который нужно произвести для обновления счетчика
- show - показывать пункт или нет (если не указан, то показывать)
- children - список дочерних пунктов меню
Используя параметр show, можно ограничивать использование пунктов меню для групп договоров:
menu( moduleId: 210, page: "modules/tv/tv", icon: "fa-tv", title: "menu.tv", show: contractInGroup( contract, [1, 2, 3, 4, 20] ) )
При необходимости список дочерних пунктов меню можно определить как переменную и добавлять пункты в этот список, используя условия:
menu { // список пунктов верхнего уровня def firstLevel = []; children = firstLevel; // Новости firstLevel << menu( page: "kernel/news", icon: "fa-newspaper-o", title: "menu.news" ) // если авторизован по логину/паролю if( isCustomer() ) { // Уведомления + Рассылки firstLevel << menu( page: "kernel/notificationsEx", subPage: "notifications", icon: "fa-envelope-o", title: "menu.notifications", badge: "#{notificationBean.getUnreadCount()}", badgeUpdate: "#{notificationBean.populate()}" ) } else { // Уведомления firstLevel << menu( page: "kernel/notifications", subPage: "", icon: "fa-envelope-o", title: "menu.notifications" ) } // Баланс firstLevel << menu( page: "kernel/balance", icon: "fa-rub", title: "menu.balance" ) // если авторизован по логину/паролю if( isCustomer() ) { // Лимит firstLevel << menu( page: "kernel/limit", icon: "fa-umbrella", title: "menu.limit" ) // Тарифные опции firstLevel << menu( page: "kernel/tariffOptions", icon: "fa-cogs", title: "menu.tariffOptions" ) ...
Обновление личного кабинета
Для обновления личного кабинета запустите скрипт mybgbilling-update.sh:
/opt/wildfly/bin/mybgbilling-update.sh
При обновлении файлы, рядом с которыми есть файл с таким же именем плюс суффикс(расширение) .orig, не будут перезаписаны файлом из сборки, вместо них обновяться .orig-файлы (см. Кастомизация нового личного кабинета). Также при обновлении полностью удаляется и перезаписывается директория MyBGBilling.war/WEB-INF/classes/ru/bitel.