🌐

GROWI - Ubuntu 24.04 への導入と Windows C# クライアント

に公開

はじめに

会社勤めをしていた時、GROWI オンプレミス利用で、部署内 Wiki 運用をしていました。
GROWI を選択した理由は、グループを用いたアクセス制御ができることと、多階層で情報が管理できることでした。

運用の一環で、GROWI API を利用して、ページ一覧(作成日・最終更新日・最終更新者・閲覧人数・Lile人数)取得を定期的に実施していました。
GROWI は 3.6.6 で運用開始して、4.5.2 にアップグレードはしましたが、下記作業などで結構手間がかかったので、それ以降、アップグレードは先延ばしとしてしまいました。

  • mongodb を 3.6→4.0→4.2→4.4 の順でアップデート
  • Bootstrap 3→4 アップデートにともなう、panel / well の card への書き換えなど

現在の最新版 7.2.0 は、4.5.2 からかなり多くの機能強化がされて、重たくなった気がしますが、機能比較される WEBコンテンツの宿命ですかね。

今回は、下記について記載したいと思います。

  • Ubuntu 24.04 に GROWI 7.2.0 をインストール
  • Windows C# で、ページ一覧(作成日・最終更新日・最終更新者・閲覧人数・Lile人数)取得

2025/04/02 追記
GROWI - データのみのバックアップ/リストア という記事も投稿しています。

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • Windows Forms - .NET Framework 4.8
  • Windows Forms - .NET 8
  • WPF - .NET Framework 4.8
  • WPF - .NET 8

GROWI は、Ubuntu 24.04 に導入します。

Ubuntu 24.04 への導入

docker, docker compose

【簡単な4つの方法】UbuntuにDockerをインストールするには |Kinsta®

まずは、docker, docker compose 環境を構築します。

$ sudo apt update
$ sudo apt install ca-certificates curl gnupg lsb-release
$ sudo mkdir -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

docker compose 操作を sudo 不要とするために、対象ユーザに docker グループを付与します。(初期インストール時は sudo が必要)

$ sudo usermod -aG docker <ユーザ>

GROWI

インストール作業前に、growi の docker-compose.yml に対する修正を記載します。
docker-compose.yml に対するミニマムな変更は、外部からのアクセスを可能とする下記修正です。

docker-compose.yml
  ports:
    # 127.0.0.1:3000:3000    # --- Changed --- #
    - 3000:3000              # --- Changed --- #

以前、オンプレミス利用時に設定した、下記内容も設定することとします。

  • PASSWORD_SEED が changeme となっているので値を指定
  • FILE_UPLOAD を local 指定
  • volumes: にホストのディレクトリを設定。docker コンテナ内にデータを隠蔽せず、ホストでアクセス可能なディレクトリにマウント。Wiki 運用開始時は GROWI としてデータアーカイブがサポートされていなかったので、この設定をすることで、データのみのバックアップが容易となりました
docker-compose.yml
  environment:
    - MONGO_URI=mongodb://mongo:27017/growi
    - ELASTICSEARCH_URI=http://elasticsearch:9200/growi
    - PASSWORD_SEED=HogePiyoFuga  # --- Changed --- #
    - FILE_UPLOAD=local           # --- Changed --- #
    # - PASSWORD_SEED=changeme    # --- Changed --- #
    # - FILE_UPLOAD=mongodb   # activate this line if you use MongoDB GridFS rather than AWS
    # - FILE_UPLOAD=local     # activate this line if you use local storage of server rather than AWS
docker-compose.yml
# volumes:
#  growi_data: 
#  mongo_configdb:
#  mongo_db:
#  es_data:
# --- Setting Changed --- #
volumes:
  growi_data:
    driver_opts:
      type: none
      device: /opt/growi/data/growi_data
      o: bind
  mongo_configdb:
    driver_opts:
      type: none
      device: /opt/growi/data/mongo_configdb
      o: bind
  mongo_db:
    driver_opts:
      type: none
      device: /opt/growi/data/mongo_db
      o: bind
  es_data:
    driver_opts:
      type: none
      device: /opt/growi/data/es_data
      o: bind

GROWI は /opt/growi を管理ディレクトリとして、growi(dockerコンテナ)、data(データマウント先)のサブディレクトリを用意します。

$ sudo -i
# cd /opt
# mkdir growi
# chown root:docker growi
# chmod 0777 growi
# cd growi
# mkdir data
# chown root:docker data
# chmod 0777 data
# cd data
# mkdir growi_data mongo_configdb mongo_db es_data
# chmod 0777 growi_data mongo_configdb mongo_db es_data
$
$ tree /opt/growi
/opt/growi
├── data
│   ├── es_data
│   ├── growi_data
│   ├── mongo_configdb
│   └── mongo_db

GROWI を取得します。

$ cd /opt/growi
$ git clone https://github.com/weseek/growi-docker-compose.git growi

記載済み ports, environment, volumes に対する変更を docker-compose.yml に反映させます。

$ cd /opt/growi/growi
$ vi docker-compose.yml

GROWI を実行します。(※初期インストール時は sudo が必要)

$ sudo docker compose up -d

動作確認と初期設定

Ubuntu 上ブラウザで http://localhost:3000 をアクセスして、下記ログイン画面が表示されることを確認します。

他マシンから http://IPアドレス:3000 でアクセスしても、同様にログイン画面が表示されることを確認後、ユーザ名などのアカウント情報を入力して、「作成」ボタンを押下してください。
ログインすると、上部に「アプリ設定」が表示されるので、選択します。

下記画面となるので、「サイトURL」を設定して「更新」を押下してください。

C# クライアント

GROWIのAPI使い方まとめ

GROWI API

GROWI は、REST API を利用して操作することが可能です。

https://docs.growi.org/en/api/

REST API v3 - Pages - getList( _api/v3/pages/list )を利用した、ページ一覧(作成日・最終更新日・最終更新者・閲覧人数・Lile人数)取得をサンプルとします。

参考:GitHub - apiv3/pages/index.js

API Token 取得

GROWI API を利用するために必要な API Token 取得方法を記載します。
まず、左下アイコン → 設定を選択します。

次画面で「API設定」、「API Tokenを更新」で API Token が表示されます。

JSON デシリアライズ

JSON デシリアライズは、現在 Microsoft が推奨している System.Text.Json を利用します。
.NET Framework の場合、System.Text.Json は標準提供されていないので、NuGet Gallery | System.Text.Json 導入が必要です。

PM> NuGet\Install-Package System.Text.Json

HttpClient

REST API クライアント実装、WebClient は .NET 6 で非推奨になったので、HttpClient を利用します。

https://learn.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient.-ctor

HttpClient は、1 回インスタンス化され、アプリケーションの有効期間中に再利用されることを目的としています。 すべての要求に対して HttpClient クラスをインスタンス化すると、大量の負荷の下で使用可能なソケットの数が使い果たされます。 これにより、SocketException エラーが発生します。

IDisposable なのに、using などで都度生成/破棄するとソケット枯渇するので、初心者の方は注意してください。

https://learn.microsoft.com/ja-jp/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use

有効期間管理の観点から推奨される HttpClient の使用方法を要約すると、PooledConnectionLifetime (.NET Core および .NET 5 以降) が設定された "有効期間の長い" クライアントを使うか、IHttpClientFactory で作成された "有効期間の短い" クライアントを使う必要があります。

なるほど、ガイドラインに従って IHttpClientFactory を利用することします。

https://learn.microsoft.com/ja-jp/dotnet/core/extensions/httpclient-factory

DependencyInjection、IHttpClientFactory を利用するので、NuGet Gallery | Microsoft.Extensions.DependencyInjectionNuGet Gallery | Microsoft.Extensions.Http を導入します。

PM> NuGet\Install-Package Microsoft.Extensions.DependencyInjection
PM> NuGet\Install-Package Microsoft.Extensions.Http

サンプルコード

.NET Framework 4.8 と .NET 8 では、軽微な差異があるので、まず .NET Framework 4.8 のサンプルコードを記載して、次に .NET 8 の差分を記載します。

.NET Framework 4.8

まずは、ServiceCollection に対して、GrowiClient という名称の HttpClient を登録した ServiceProvider を用意します。

private static IServiceProvider MyServiceProvider = null;
var serviceCollection = new ServiceCollection();
serviceCollection.AddHttpClient("GrowiClient", client =>
{
  client.BaseAddress = new Uri("http://192.168.11.135:3000/"); // TODO - GROWI ベースアドレス
  client.DefaultRequestHeaders.Add("Accept", "application/json");
});
MyServiceProvider = serviceCollection.BuildServiceProvider();

REST API v3 - Pages - getList( _api/v3/pages/list )クライアント処理を用意します。

// REST API v3 - Pages - getList( _api/v3/pages/list )
private async Task<List<ResponsePage>> GrowiGetPageList(string accessToken)
{
  // IHttpClientFactory で GrowiClient という名前の HttpClient 取得
  var factory = MyServiceProvider?.GetService<IHttpClientFactory>();
  var httpClient = factory?.CreateClient("GrowiClient");
  if (httpClient == null)
  {
    // エラー発生 - TODO
    return null;
  }

  string baseUrl = "_api/v3/pages/list";
  var encodedToken = Uri.EscapeDataString(accessToken);
  var lstPages = new List<ResponsePage>();
  int totalCount = 0; // 全件数
  int limit = 0;      // 1回の取得件数(途中で能動的に変化はしない)
  int count = 1;      // ループ回数(1~)

  // 1回の処理は limit 件のページ取得なので、ループ処理とする
  while (true)
  {
    string targetUrl = baseUrl + $"?access_token={encodedToken}&path=/&page={count}";
    using (var request = new HttpRequestMessage(HttpMethod.Get, targetUrl))
    using (var response = await httpClient.SendAsync(request))
    {
      if (response?.IsSuccessStatusCode == true)
      {
        var content = response.Content.ReadAsStringAsync().Result;
        var obj = System.Text.Json.JsonSerializer
                    .Deserialize<ResponsePageList>(content);
        if (obj == null)
        {
          // エラー発生 - TODO
          return null;
        }
        if (limit == 0) // 初回
        {
          totalCount = obj.totalCount;
          limit = obj.limit;
        }
        if (obj.pages?.Count > 0)
        {
          foreach (ResponsePage page in obj.pages)
          {
            lstPages.Add(page);
          }
        }
      }
      else
      {
        // エラー発生 - TODO
        return null;
      }
    }
    // 残りがあるか?
    if (totalCount <= (count++ * limit))
    {
      break;
    }
  }
  return lstPages;
}
// JSON 用クラス
public class ResponsePageList
{
  public int totalCount { get; set; }          // 総件数
  public int offset { get; set; }              // 現在取得した情報の先頭からのオフセット
  public int limit { get; set; }               // 1回の取得件数
  public List<ResponsePage> pages { get; set; }
}
public class ResponsePage
{
  // 利用する項目のみ定義
  public string path { get; set; }                 // 対象ページ
  public DateTime createdAt { get; set; }          // 作成日時
  public DateTime updatedAt { get; set; }          // 更新日時
  public ResponseUser lastUpdateUser { get; set; } // 最終更新ユーザ
  public List<string> seenUsers { get; set; }      // 閲覧者一覧 - User._id のリスト
  public List<string> liker { get; set; }          // Liker一覧 - User._id のリスト
}
public class ResponseUser
{
  // 利用する項目のみ定義
  public string username { get; set; }    // ユーザID(username)
  public string name { get; set; }        // 名前
}

上記処理を以下のように呼び出します。

string accessToken = "kKJoYh8XrTsT2eMMNcfmWzljqtGWTpXweAzqotlJeug=";  // TODO - API Token

// REST API v3 - Pages - getList( _api/v3/pages/list )
var lstPages = await GrowiGetPageList(accessToken);
// TODO - CSV出力など...

.NET 8

.NET 8 では、下記差分があるので、サンプルコードの一部を書き換えます。

  • null 許容参照型の明示
    • MyServiceProvider、GrowiGetPageList 戻り値、および、JSON 用クラス
private static IServiceProvider? MyServiceProvider = null;
// REST API v3 - Pages - getList( _api/v3/pages/list )
private async Task<List<ResponsePage>?> GrowiGetPageList(string accessToken)
{
<中略>
}
// JSON 用クラス
public class ResponsePageList
{
  public int totalCount { get; set; }          // 総件数
  public int offset { get; set; }              // 現在取得した情報の先頭からのオフセット
  public int limit { get; set; }               // 1回の取得件数
  public List<ResponsePage>? pages { get; set; }
}
public class ResponsePage
{
  // 利用する項目のみ定義
  public required string path { get; set; }         // 対象ページ
  public DateTime createdAt { get; set; }           // 作成日時
  public DateTime updatedAt { get; set; }           // 更新日時
  public ResponseUser? lastUpdateUser { get; set; } // 最終更新ユーザ
  public List<string>? seenUsers { get; set; }      // 閲覧者一覧 - User._id のリスト
  public List<string>? liker { get; set; }          // Liker一覧 - User._id のリスト
}
public class ResponseUser
{
  // 利用する項目のみ定義
  public required string username { get; set; }    // ユーザID(username)
  public required string name { get; set; }        // 名前
}

出典

本記事は、2025/03/31 Qiita 投稿記事の転載です。

GROWI - Ubuntu 24.04 への導入と Windows C# クライアント

Discussion