はじめに
会社勤めをしていた時、GROWI オンプレミス利用で、部署内 Wiki 運用をしていました。
GROWI を選択した理由は、グループを用いたアクセス制御ができることと、多階層で情報が管理できることでした。
個人としても 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 への書き換えなど
アップグレードの情報は、GROWI DOCS - アップグレード に記載されています。
現在利用のバージョンから、最新バージョンまでの各バージョンで必要となる作業を確認することができます。
現在の最新版 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
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
GROWI は、Ubuntu 24.04 に導入します。
Ubuntu 24.04 への導入
docker, docker compose
まずは、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 に対するミニマムな変更は、外部からのアクセスを可能とする下記修正です。
ports:
# 127.0.0.1:3000:3000 # --- Changed --- #
- 3000:3000 # --- Changed --- #
以前、オンプレミス利用時に設定した、下記内容も設定することとします。
- PASSWORD_SEED に値を指定(他マシンからの移行時には同一値を設定)
- FILE_UPLOAD を local 指定
- volumes: にホストのディレクトリを設定。docker コンテナ内にデータを隠蔽せず、ホストでアクセス可能なディレクトリにマウント。Wiki 運用開始時は GROWI としてデータアーカイブがサポートされていなかったので、この設定をすることで、データのみのバックアップが容易となりました
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
# 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
/opt/growi 下の growi に 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 は、REST 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 を利用します。
HttpClient は、1 回インスタンス化され、アプリケーションの有効期間中に再利用されることを目的としています。 すべての要求に対して HttpClient クラスをインスタンス化すると、大量の負荷の下で使用可能なソケットの数が使い果たされます。 これにより、SocketException エラーが発生します。
IDisposable なのに、using などで都度生成/破棄するとソケット枯渇するので、初心者の方は注意してください。
有効期間管理の観点から推奨される HttpClient の使用方法を要約すると、PooledConnectionLifetime (.NET Core および .NET 5 以降) が設定された "有効期間の長い" クライアントを使うか、IHttpClientFactory で作成された "有効期間の短い" クライアントを使う必要があります。
なるほど、ガイドラインに従って IHttpClientFactory を利用することします。
DependencyInjection、IHttpClientFactory を利用するので、NuGet Gallery | Microsoft.Extensions.DependencyInjection と NuGet 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; } // 名前
}