Настройка нового личного кабинета
Конфигурация ЛК состоит из трех файлов, расположенных в папке 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
=
value
1
one.two.three.parameterA
=
value
2
one.two.three.parameterB
=
100
Некоторые значения параметров должны быть списками или массивами определенных объектов. Объекты списка заключены в квадратные скобки [] и разделены между собой символом запятой. Например:
authentication {
modes
=
[
authenticationMode {
mode
=
'contract'
},
authenticationMode {
mode
=
'login'
module
=
'inet'
moduleId
=
1
}
]
}
Некоторые значения параметров могут быть ассоциативными массивами (список ключ:значение, map). Связки ключ:значение заключены в квадратные скобки [] и разделены между собой символом запятой. Например:
example {
map
=
[
key
:
'value'
,
key
2
:
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-соединения
Личному кабинету в некоторых случаях требуется знать IP-адрес абонента, который пользуется им в текущий момент (например, для авторизации по IP-адресу или блокировке при переборе логинов/паролей). Поэтому при использовании NGINX требуется указать HTTP-заголовок в параметре context.hostHttpRequestHeader, из которого получать реальный IP-адрес вместо физического IP-адреса HTTP-соединения.
context {
...
// Идентификатор хоста по HTTP-заголовку, например, X-Real-IP. По умолчанию используется IP-адрес хоста
hostHttpRequestHeader
=
'X-Real-IP'
}
Параметры аутентификации абонента
// Параметры аутентификации абонента
authentication {
// Кол-во ошибок аутентификации, после которого будет отображаться captcha для этого логина
captchaLoginErrorCount
=
5
// Кол-во ошибок аутентификации, после которого будет отображаться captcha для хоста
captchaHostErrorCount
=
20
// Кол-во ошибок аутентификации, после которых будут заблокированы попытки этого хоста
blockHostErrorCount
=
30
// Режимы аутентификации для входа в ЛК
modes
=
[
// аутентификация по номеру договора
authenticationMode {
mode
=
'contract'
}
]
}
Режимов аутентификации может быть несколько - в этом случае в окне логина можно выбрать необходимый. На данный момент поддерживаются три режима аутентификации:
по номеру договора
authenticationMode {
mode
=
'contract'
}
по логину модуля Inet
authenticationMode {
module
=
'inet'
mode
=
'login'
// ID модуля
moduleId
=
1
}
по IP-адресу сессии модуля Inet (вход без пароля)
authenticationMode {
module
=
'inet'
mode
=
'ip'
// ID модуля
moduleId
=
1
// ограниченный доступ
role
=
'unauthCustomer'
}
Для режима аутентификации можно назначить, чтобы доступ после аутентификации через него был ограничен. Для этого указывается параметр 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"
)
...