2.4 Описание логической структуры программного продукта
Базы данных IBM Domino являются документ-ориентированными. Кроме документов в базе данных хранятся служебные объекты (формы, представления, формы) и ресурсы (рисунок 2.12).
Рисунок 2.12 – Логическая структура базы данных IBM Domino
Документ является именованным набором пар ключ-значение. Набор полей зависит от формы, в которой он был создан. Поэтому в разрабатываемом продукте необходимо реализовать возможность настройки отображения документов в зависимости от родительской формы.
Документы в базе данных могут быть объединены в коллекции, именуемые представлениями (англ. view). Логически, представления являются предварительно сконфигурированными выборками, к которым можно применять сортировку и фильтрование по значению столбца.
Условие (формула) для выборки подмножества из множества всех документов задается в свойствах представления, при редактировании базы данных в программном обеспечении IBM Domino Designer.
Между документами возможны два вида связей: ответ на документ и ответ на ответ. Не все прикладные интерфейсы IBM Domino позволяют извлекать информацию о связях между документами.
Всего в документации IBM Domino [13] определено 16 основных типов полей. Данные типы перечислены в таблице 2.2.
Таблица 2.2 – Типы полей IBM Domino
Тип поля
|
Описание
|
Флажок
(Checkbox)
|
Имеет два состояния: отмечен и не отмечен
|
Цвет
|
Служит для выбора цвета из палитры цветов
|
Выпадающий список
(ComboBox)
|
Выбор элемента из списка в виде выпадающего меню
|
Список в окне
(Dialog List)
|
Выбор элемента из списка в диалоговом окне
|
Формула
|
Отображает результат заданных вычислений
|
Список
|
Отображает список элементов
|
Пароль
|
Редактирование скрытого под звездочками текста
|
Число
|
Числовой тип данных
|
Переключатель
(RadioButton)
|
Используется в группах для выбора чего-либо
|
Текст
|
Поле для ввода простого текста
|
Форматированный текст
(с панелью формата)
|
Поле ввода форматированного текста.
Отображается панель управления форматом.
Поддерживается прикрепление файлов
|
Форматированный текст
(без панели формата)
|
Аналогично предыдущему типу, но нет панели формата.
Форматирование при помощи меню приложения IBM Notes.
|
Временная зона
|
Выбор временной зоны
|
Имена
|
Отображение или редактирование списка пользователей
|
Авторы
|
Используется в формах для управления доступом
|
Читатели
|
Используется в формах для управления доступом
|
Для текстовых полей сервер выполняет построение полнотекстового индекса для поиска (англ. Full Text Index – FTI). Этот индекс используется при полнотекстовом поиске по базе данных.
2.5 Функциональная схема, функциональное назначение
программного продукта
Функциональным назначением продукта является предоставление оперативного доступа к документам из государственных реестров, расположенных на серверах под управлением IBM Domino 9.0.
Функциональная схема представлена на рисунке 2.13.
Рисунок 2.13 – Функциональная схема программного продукта
Программный продукт должен выполнять следующие функции:
аутентификация по логину и паролю IBM Domino;
отображение доступных баз данных на авторизированных серверах;
отображение списка доступных представлений;
отображение в табличном виде документов в представлениях;
полнотекстовый поиск документов;
печать документа и списка документов.
3 РЕАЛИЗАЦИЯ И ТЕСТИРОВАНИЕ ПРОГРАММНОГО ПРОДУКТА
3.1 Описание реализации
Разработанный программный продукт состоит из двух логических частей: веб-сервера и веб-приложения. Веб-сервер запускается на служебном компьютере, а веб-приложение загружается по сети в браузере пользователя.
Ниже представлен код инициализации HTTP-сервера.
func runHttp(hostname string, port int) {
m := http.DefaultServeMux
m.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
http.ServeContent(w, req, assetName, fi.ModTime(), bytes.NewReader(data)) })
rest.ServeApi(m)
addr := fmt.Sprintf("%s:%d", hostname, port)
s := &http.Server{
Addr: addr,
ReadTimeout: 60 * time.Second,
WriteTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20, }
log.Printf("running server at http://%s...\n", addr)
log.Fatal(s.ListenAndServe()) }
Листинг 3.1 – Инициализация веб-сервера
При инициализации веб-сервера устанавливаются обработчики для отдачи статического содержимого и прикладного интерфейса веб-приложения. Если в конфигурационном файле включен защищенный режим, в память загружается TLS-сертификат [18]. Возможно использование как самоподписанных, так и подписанных третьей стороной сертификатов.
Запуск сервера блокирует выполнение главной горутины. Для обработки запросов на сервере создается по отдельной горутине на один входящий запрос.
m.HandleFunc("/rest/state", restState)
m.HandleFunc("/rest/login", restLogin)
m.HandleFunc("/rest/logout", restLogout)
m.HandleFunc("/rest/databases", authorized(restDatabases))
Листинг 3.2 – Установка обработчиков прикладного интерфейса
Установка обработчиков выполняется при помощи метода HandleFunc(path, func). Для обработчиков, которые недоступны пользователям, не прошедшим аутентификацию написаны два вспомогательных метода authorized и authorizedSingleRemote. Метод authorizedSingleRemote в отличие от authorized, не только получает список активных сессий, но и определяет в рамках какой сессии должен работать обработчик.
Для представления сессий были описаны три пользовательские структуры: Sessions, ApplicationSession и DominoSession. Они представляют список активных сессий, сессию приложения и сессию сервера IBM Domino соответственно. В рамках сессии приложения выполняется настройка программного обеспечения под структуру конкретной базы данных IBM Domino. Получение сессии приложения возможно только при одной и более активных сессий Domino. Такое ограничение установлено в связи с тем, что приложение не является самодостаточным и работа без подключения к IBM Domino невозможна.
Оба вида сессий расширяют тип Session, для которого реализованы методы проверки на срок действия, установки и удаления cookies.
func (s *Session) isExpired() bool {
return s.expires.Before(time.Now()) }
func (s *Session) setCookie(w http.ResponseWriter) {
http.SetCookie(w, cookie(s.cookieName, s.id, s.expires)) }
func (s *Session) deleteCookie(w http.ResponseWriter) {
http.SetCookie(w, cookie(s.cookieName, "", ZeroTime)) }
Листинг 3.3 – Методы структуры Session
Метод isExpired используется для проверки того, что сессия еще является активной. Методы setCookie и deleteCookie применяются для установки и удаления Cookies в браузере. Удаление осуществляется путем присвоения срока действия в прошедшем времени (на листинге 3.3 устанавливается 1 секунда эры Unix, Определяется как количество секунд, прошедших с полуночи (00:00:00 UTC) 1 января 1970 года).
Для определения текущего пользователя по списку переданных Cookies были реализованы методы инициализации списка активных сессий.
Ниже представлен код, инициализирующий список активных сессий для текущего запроса:
func startSessions(w http.ResponseWriter, req *http.Request) *Sessions {
ss := Sessions{}
for _, cook := range req.Cookies() {
if cook.Name == ApplicationSessionCookieName {
if id, err := url.QueryUnescape(cook.Value); err == nil {
if s, ok := appSessionStore[id]; ok && !s.isExpired() {
ss.app = s }
} else if strings.HasPrefix(cook.Name, SessionCookiePrefix) {
/* ... */} }
if ss.app == nil {
ss.app = &ApplicationSession{} }
now := time.Now()
expire := now.Add(SessionMinActivityTime)
if tt := ss.app.expires.Sub(now); tt > 0
&& tt < SessionMinActivityTime {
ss.app.expires = expire
ss.app.setCookie(w)
debug.Println("Application session prolonged") }
for _, ds := range ss.domino {
/* ... */}
return &ss }
Листинг 3.4 – Зарузка сессий для текущего запроса и продление сессий
Если отсутствует сессия приложения, то инициализируется пустая сессия (вызов isExpired вернет истину). Это позволяет вместо двух проверок использовать только одну, так как проверка на nil становится ненужной.
В пакете domino реализован транспортный уровень для взаимодействия с IBM Domino. Для этого в данном пакете определена структура Client представляющая совокупность удаленной точки и данных для аутентификации. Над структурой Client определены методы для работы с транспортным уровнем.
Для получения данных от хранилища данных используется клиент для протокола HTTP реализованных в стандартной библиотеке языка.
Для подключения к IBM Domino по протоколу HTTPS используется системное хранилище сертификатов. Для обработки ошибок соединения, устанавливается максимальное время ожидания.
Получение данных реализовано в отдельном методе, который представлен ниже. Данный метод возвращает канал и ожидает ответа IBM Domino в отдельном потоке. После того как результат получен, он отправляется в канал.
func load(req request) (ch chan resource) {
url := req.url()
httpReq, _ := http.NewRequest("GET", url, nil)
httpReq.Header.Set("Accept", "application/json")
if req.client.Credentials.Cookies != "" {
httpReq.Header.Set("Cookie", req.client.Credentials.Cookies) } else {
httpReq.SetBasicAuth(req.client.Credentials.Username, req.client.Credentials.Password) }
go func() {
defer func() { <-requestLimit }()
httpClient := http.Client{Timeout: DominoTimeout}
resp, err := httpClient.Do(httpReq)
ch <- resource{
cookies: strings.Join(cookies, ";"),
cursor: cursor,
data: data, } }()
return }
Листинг 3.5 – Использование клиента для протокола HTTP
Данные от сервера IBM Domino приходят в формате JSON. Поэтому в пакете описаны структуры, помеченные специальными тегами для автоматического декодирования из JSON строки.
type DominoDocument struct {
Href string `json:"@href"`
Unid string `json:"@unid"`
NoteId string `json:"@noteid"`
Created iso8601_datetime `json:"@created"`
Modified iso8601_datetime `json:"@modified"`
Authors []DominoUser `json:"@authors"`
Form string `json:"@form"`
Items jsonext.CatchAll `jsonext:"catchall"` }
var list []DominoDocument
if err := jsonext.Unmarshal(res.data, &list); err != nil {
log.Printf("%s\n%s\n", err, res.data[:1000])
ch <- DocumentList{Err: err}
return }
Листинг 3.6 – Пример помеченной структуры и декодирования данных
Поля структуры помечены тегом json, с указанием названия поля ответа. Тэг jsonext:"catchall" определяет поле структуры, в которое будут записаны не опознанные поля ответа. У документа эти поля будут представлять его данные.
3.2 Описание пользовательского интерфейса
Пользовательский интерфейс представлен совокупностью файлов JavaScript, HTML и CSS, содержащих соответственно программный код, разметку и каскадные таблицы стилей.
Стили описываются при помощи языка Less. Файлы стилей преобразуются в каскадные таблицы стилей CSS на этапе сборки веб-приложения, и объединяются в один файл с именем main.css.
Каскадные таблицы состоят из списка правил. Каждое правило содержит селектор и набор стилей. Селектор – это часть правила, которая описывает к каким элементам дерева документа эти стили необходимо применить. Конечный стиль элемента определяется путем каскадного объединения всех правил вместе, при этом одинаковые свойства принимают значение наиболее приоритетного правила.
a { color: @light-black;
text-decoration: none;
outline: none;
&:focus { background: @grey-200; }
&:hover{
color: @white;
background: @min-black;
}
&:active {
color: @white;
background: @light-black; } }
Листинг 3.7 – Пример описания стилей на языке Less
Язык Less в отличие от CSS поддерживает вложенные правила, переменные и функции. Вложенные правила удобны для описания различных состояний элемента (в примере, «&:hover» скомпилируется в «a:hover»), а переменные (в примере они начинаются с символа @) применяются для хранения каких-либо значений, общих для нескольких правил.
Так как приложение является одностраничным, то имеется один файл index.html, в котором производится подключение ресурсов и вызов кода инициализирующего приложение.
|