1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

プリザンターに蓄積されたレコードやドキュメントを横断的に検索したい、というニーズは少なくありません。プリザンター標準のビュー絞り込みは強力ですが、複数テーブルにまたがる全文検索や、添付ファイルの中身まで検索したい場合には、専用の全文検索エンジンを組み合わせるのが効果的です。

今回は、オープンソースの全文検索サーバー Fess をプリザンターと連携させてみます。FessはWebページやファイルサーバ、DBなど多様なデータソースをクロールしてインデックス化し、シンプルなAPIで検索結果を返してくれます。プリザンターのスクリプト機能からFessの検索APIを呼び出すことで、プリザンター画面内に全文検索ボックスを追加できます。

この記事では以下を説明します。

  • FessのWebクローラーでプリザンターをインデックス化する設定
  • プリザンターのサーバスクリプトからFess検索APIを呼び出す実装
  • 検索結果をプリザンターの画面に表示するスクリプト実装

全体構成

FessのWebクローラーがプリザンターの画面をクロールしてインデックスを構築します。ユーザーがプリザンター画面上の検索ボックスにキーワードを入力すると、プリザンターのスクリプトがFessの検索APIを呼び出し、結果をその場に表示する仕組みです。

前提条件

項目 内容
プリザンター v1.3 以降(サーバスクリプト対応版)
Fess v14.x 以降
ネットワーク プリザンターサーバ ↔ Fessサーバ間で HTTP 通信が可能

Fessのインストール・初期設定については本記事では扱いません。Fess公式ドキュメントを参照してセットアップを完了させてください。

Fess の設定

JSON レスポンスの有効化

FessのデフォルトではJSON APIが無効になっています。管理画面から有効化します。

  1. Fess管理画面にログイン(http://<Fessサーバ>/admin/
  2. 「システム」→「全般」を開く
  3. 「JSON レスポンス」をオンにして保存

これで http://<Fessサーバ>/json/?q=検索ワード でJSONを取得できるようになります。

CORS の設定(別ドメインの場合)

プリザンターとFessが異なるホストにある場合は、FessのCORS設定が必要です。

fess_config.properties$FESS_HOME/app/WEB-INF/classes/)に以下を追記します。

fess_config.properties
api.cors.allow.origin=https://<プリザンターのドメイン>
api.cors.allow.methods=GET
api.cors.allow.headers=Content-Type
api.cors.allow.credentials=false

ワイルドカード(*)はセキュリティリスクがあるため、本番環境ではプリザンターのオリジンを明示的に指定することを推奨します。

Webクローラーの設定

FessのWebクローラーでプリザンターをインデックス化します。

  1. 管理画面の「クローラー」→「ウェブ」→「新規作成」を選択
  2. 以下を設定して保存
設定項目 設定値
名前 Pleasanter
URL https://<プリザンターのURL>/items/
対象URLパターン https://<プリザンターのURL>/items/.*
除外URLパターン .*\.(css|js|png|jpg|gif|ico)
最大アクセス数 適切な値(例: 1000)
ユーザエージェント Fessbot

認証設定(ログインが必要な場合)

プリザンターがログイン必須の場合、クローラーにフォーム認証を設定します。

  1. 「クローラー」→「設定」→「ウェブ認証」→「新規作成」
  2. 以下を設定
設定項目 設定値
ホスト名 <プリザンターのホスト>
URL https://<プリザンターのURL>/users/sign_in
認証タイプ フォーム
ユーザ名パラメータ LoginId
パスワードパラメータ Password
パラメータ LoginId=<クローラー用ユーザ名>&Password=<パスワード>

クローラー用に閲覧専用の専用ユーザを作成し、必要なテーブルのみ閲覧権限を付与することを推奨します。

クロールの実行

設定後、管理画面の「システム」→「クローラー」から「今すぐ開始」を押してクロールを実行します。完了後、「Fess 検索」ページでキーワードを入力して検索結果が表示されることを確認します。

プリザンターへの検索ボックス実装

プリザンターのスクリプト・サーバスクリプト機能を使って、Fessの検索機能をプリザンター画面に組み込みます。ここでは「検索ボックスと結果一覧を同じ画面に表示する」構成を実装します。

サーバスクリプト(検索の実行)

サーバスクリプトからFessのAPIを呼び出すことで、サーバサイドで検索を実行できます。クライアントスクリプトからの直接呼び出しと比較して、FessサーバのURLやアクセストークンをフロントエンドに露出させずに済むメリットがあります。

テーブルの「テーブルの管理」→「サーバスクリプト」タブで以下を設定します。

項目 設定値
タイトル Fess検索
条件 一覧(表示前)
サーバスクリプト 下記参照
// --- 設定 ---
var fessUrl = 'http://<Fessサーバ>:8080/json/';
var maxResults = 10;
// --- 設定ここまで ---

// クエリパラメータから検索ワードを取得
var query = model.GetParam('fq') || '';
if (!query) {
    model.SetParam('fess_results', '[]');
    model.SetParam('fess_total', '0');
    return;
}

// Fess 検索APIを呼び出す
httpClient.ResponseHeaders.Clear();
httpClient.RequestUri = fessUrl
    + '?q=' + encodeURIComponent(query)
    + '&num=' + maxResults;
var response = httpClient.Get();

if (!httpClient.IsSuccess) {
    context.Log('Fess API エラー: ' + httpClient.StatusCode);
    model.SetParam('fess_results', '[]');
    model.SetParam('fess_total', '0');
    return;
}

var json = JSON.parse(response);
var results = json.response.result || [];
var total = json.response.record_count || 0;

model.SetParam('fess_results', JSON.stringify(results));
model.SetParam('fess_total', String(total));

httpClient.ResponseHeaders.Clear() はリクエスト前に呼び出してください。詳しくは「プリザンターのサーバスクリプトでhttpClientを使うときのお約束」を参照してください。

スクリプト(検索ボックスの追加と結果表示)

テーブルの「テーブルの管理」→「スクリプト」タブで以下を設定します。

項目 設定値
タイトル Fess検索UI
条件 一覧
スクリプト 下記参照
(function () {
    'use strict';

    // サーバスクリプトが埋め込んだ検索結果を取得する
    var resultsJson = $('[data-fess-results]').data('fess-results') || '[]';
    var totalStr = $('[data-fess-results]').data('fess-total') || '0';
    var results = [];
    try {
        results = JSON.parse(resultsJson);
    } catch (e) {
        results = [];
    }
    var total = parseInt(totalStr, 10) || 0;

    // 現在の検索ワードを URL パラメータから取得
    var params = new URLSearchParams(window.location.search);
    var currentQuery = params.get('fq') || '';

    // 検索ボックスのHTML
    var searchBoxHtml = [
        '<div id="fess-search-box" style="margin:8px 0;display:flex;gap:8px;align-items:center;">',
        '  <input type="text" id="fess-query" placeholder="全文検索..." value="'
            + $('<div/>').text(currentQuery).html()
            + '" style="padding:4px 8px;border:1px solid #ccc;border-radius:4px;width:240px;">',
        '  <button id="fess-search-btn" style="padding:4px 12px;cursor:pointer;">検索</button>',
        '</div>'
    ].join('');

    // 検索ボックスを一覧上部に挿入
    $('#ViewFilters').before(searchBoxHtml);

    // 検索ボタンのクリックイベント
    $('#fess-search-btn').on('click', function () {
        var q = $('#fess-query').val().trim();
        if (!q) return;
        var url = new URL(window.location.href);
        url.searchParams.set('fq', q);
        window.location.href = url.toString();
    });

    // Enter キーでも検索実行
    $('#fess-query').on('keydown', function (e) {
        if (e.key === 'Enter') {
            $('#fess-search-btn').trigger('click');
        }
    });

    // 検索結果の表示
    if (results.length === 0 && currentQuery) {
        $('#MainForm').after(
            '<div id="fess-results"><p>「'
                + $('<div/>').text(currentQuery).html()
                + '」に一致する結果が見つかりませんでした。</p></div>'
        );
        return;
    }

    if (results.length === 0) return;

    var html = [
        '<div id="fess-results" style="margin-top:12px;padding:12px;border:1px solid #ddd;border-radius:4px;">',
        '  <p style="margin:0 0 8px;font-weight:bold;">全文検索結果: '
            + $('<div/>').text(currentQuery).html()
            + '' + total + '件中 ' + results.length + '件表示)</p>',
        '  <ul style="margin:0;padding:0;list-style:none;">'
    ];

    results.forEach(function (item) {
        html.push(
            '<li style="margin-bottom:8px;padding:8px;background:#f9f9f9;border-radius:4px;">',
            '  <a href="' + $('<div/>').text(item.url_link).html() + '" target="_blank" rel="noopener noreferrer"',
            '     style="font-weight:bold;color:#1a6496;text-decoration:none;">'
                + $('<div/>').text(item.title || item.url_link).html() + '</a>',
            '  <p style="margin:4px 0 0;font-size:0.85em;color:#555;">'
                + (item.content_description || item.digest || '') + '</p>',
            '</li>'
        );
    });

    html.push('  </ul>', '</div>');

    $('#MainForm').after(html.join(''));
})();

スクリプト内でHTMLを生成する際、ユーザー入力値は必ず $('<div/>').text(value).html() でエスケープしてください。XSS(クロスサイトスクリプティング)を防ぐために重要です。

サーバスクリプトの結果を画面に埋め込む

サーバスクリプトで model.SetParam に格納した値をスクリプトから参照するには、スクリプトでアクセスできる形にする必要があります。プリザンターの model.GetParam / model.SetParam は一覧の「表示前」サーバスクリプトで設定した値を data- 属性として出力する方法が一般的です。

テーブルの「テーブルの管理」→「拡張HTML」に以下を追加し、サーバスクリプトで設定した値をHTML属性に書き出します。

拡張HTML(一覧画面 - 本文上部)
<div id="fess-data"
     data-fess-results="{{ParamFess_results}}"
     data-fess-total="{{ParamFess_total}}"
     style="display:none;"></div>

プリザンターの拡張HTMLでサーバスクリプトのパラメータを参照するには {{Param<パラメータ名>}} の形式を使います。パラメータ名は SetParam の第1引数を頭文字大文字にした形になります。

動作確認

  1. 一覧画面を開き、検索ボックスが表示されることを確認
  2. キーワードを入力して「検索」ボタンをクリック
  3. 画面下部にFessの検索結果が表示されることを確認

応用: ラベルによる絞り込み

FessにはURLパターンをグループ化する「ラベル」機能があります。fields.label パラメータを使うと、特定ラベルのドキュメントのみに絞り込んで検索できます。

// テーブルIDをラベルとして設定(Fessのラベル設定が必要)
var siteId = context.SiteId;
httpClient.RequestUri = fessUrl
    + '?q=' + encodeURIComponent(query)
    + '&fields.label=site_' + siteId
    + '&num=' + maxResults;

これにより、複数のプリザンターテーブルがインデックスされている環境でも、現在のテーブルに関連するドキュメントのみを検索対象にできます。

まとめ

FessとプリザンターのサーバスクリプトをHTTP API経由で連携することで、プリザンター画面上から全文検索を実行できるようになりました。

  • FessのJSON APIをサーバスクリプトの httpClient から呼び出すことで、Fessサーバのアドレスをクライアントに露出せずに検索を実行できる
  • プリザンターのWebクローラー設定でプリザンター自体のコンテンツをインデックス化できる
  • スクリプトで検索ボックスと結果表示UIを動的に追加できる
  • fields.label を活用することでテーブル単位の絞り込みも可能

Fessはファイルサーバ(SMB/NFS)やSharePoint、Confluenceなど多様なデータソースに対応しているため、プリザンターのデータに限らず組織の情報資産を横断的に検索する基盤として活用できます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?