
Questetra BPM Suite の Workflow API を利用して、「条件に一致するプロセスでアップロードされたファイルをすべてダウンロードする」スクリプトを紹介します。例えば、次のようなダウンロード条件を実装できます。
- あるワークフローアプリで、今期開始されたプロセスにてアップロードされたファイル
- 特定のユーザによって開始されたプロセスでアップロードされたファイル
大まかな手順は以下の通りです。
- 検索条件を表す XML データ (criteria) を用意
- プロセス検索を行う API エンドポイントを用いて、条件に一致するプロセスのデータを取得
- 取得したプロセスデータからファイルに関するデータ(ex: ファイル ID)を抽出
- ファイルダウンロードを行う API エンドポイントを用いて、目的のファイルをダウンロード
必須環境
- Questetra BPM Suite で Basic 認証が有効になっていること
- 有効化の方法は「ユーザ情報をローカルデータと同期させる(準備編)」参照
シェルスクリプトを用いる場合
- jq コマンド
- Windows の場合は Windows Subsystem for Linux 上で curl コマンドが動作すること
Python を用いる場合
- Python (3.x系)
- Requests ライブラリ
条件に一致するプロセスを検索する
Workflow API を用いてプロセス検索をする方法の詳細は「Workflow API を用いてプロセスやタスク一覧をダウンロードする」で紹介しています。検索条件を示す criteria という XML データの用意や、curl コマンドによる Basic 認証、API アクセスの方法についてはこちらをご覧ください。
今回使用する API エンドポイントは /API/OR/ProcessInstance/list(JSON, UTF-8)です。
ファイルをダウンロードする
ファイルダウンロードのための API エンドポイントは /API/OR/ProcessInstance/File/download ですが、このエンドポイントは 2 つのパラメータを取ります。
- id: ファイルに付与された ID
- processDataInstanceId: 各プロセスの各データ項目に付与された ID
この 2 つのパラメータはプロセス検索結果の JSON から抽出することができます。結果の例を以下に挙げますので、確認してみましょう。見やすさのためにプロセス 1 件分のみ抜き書いています。
プロセス検索API 応答例
[
{
"activeTokenNodeName": null,
"data": {
"1": { ← データ項目番号(モデラーに表示されているもの)
"dataType": "STRING", ← 文字型データ項目
...(中略)...
},
"2": { ← データ項目番号(モデラーに表示されているもの)
"dataType": "FILE2", ← ファイル型データ項目
"id": 9143, ← processDataInstanceId と同じ
"processDataDefinitionNumber": 2,
"subType": null,
"value": [
{
"contentType": "image/png",
"id": 9149, ← ファイルに付与された ID
"image": true,
"length": 71550,
"lengthText": "69.9 KB",
"name": "ques-kun-01.png", ← ファイル名
"processDataInstanceId": 9143 ← 各プロセスの各データ項目に付与された ID
},
{
"contentType": "text/plain",
"id": 9150, ← ファイルに付与された ID
"image": false,
"length": 1056,
"lengthText": "1.1 KB",
"name": "README.txt", ← ファイル名
"processDataInstanceId": 9143 ← 各プロセスの各データ項目に付与された ID
}
],
"viewOrder": 2
}
},
...,
"processInstanceId": 183,
...,
"processInstanceTitle": "テストです",
...,
"processModelInfoName": "テスト用: ファイルダウンロード",
...
},
...
]
データ項目を取得したい場合、criteriaで取得対象データのデータ項目番号を指定する必要があります。「Workflow API を用いてプロセスやタスク一覧をダウンロードする」の手順に沿ってcriteriaを取得する場合、Web UIの検索画面で表示項目エリアを開き、ファイル型データ項目にチェックを入れた状態で「更新」ボタンをクリックし、最新のcriteriaを取得してください(データ項目に一つもチェックが入っていない状態で取得したcriteriaを使用すると、”data”:nullの結果が返されます)。
データ項目のオブジェクトは、データ項目番号をキーとしたマップに格納されています。

dataType が FILE2 であるオブジェクトがファイル型データ項目を表しています。ques-kun-01.png と README.txt がアップロードされたプロセスだということがわかりますね。id と processDataInstanceId もこのオブジェクトに格納されています。JSON をパースしてこの 2 項目を抽出すれば、API アクセスのためのパラメータが揃います。
ファイルダウンロード API にアクセスする
API エンドポイントに id, processDataInstanceId の組を送信することで、該当するファイルをダウンロードできます。必要なパラメータが違うだけで、プロセス検索 API と同じようにアクセスできます。API レスポンスはファイルのバイナリデータなので、適宜保存してください。
以下にプロセス検索からファイルダウンロードまでを行うサンプルコードを掲載します。シェルスクリプトと Python の 2 例です。criteria(検索条件)に一致するプロセスでアップロードされたファイルをすべてダウンロードするスクリプトです。ファイルの保存ディレクトリはプロセス毎に分けています。

プロセスへの添付ファイルの一括ダウンロードが必要になった際、以下のコードを参考にしてみてください!
シェルスクリプト コード例
#!/bin/sh
# プロセス検索のためのスクリプト
# -u: Basic認証オプション
# パラメータ...criteria: 検索条件XML, start: 検索結果の取得開始位置, limit: 最大取得件数
process_search=$(curl -u {メールアドレス}:{Basicパスワード}\
'https://example.questetra.net/API/OR/ProcessInstance/list'\
--data-urlencode criteria@process-criteria.txt\
--data-urlencode 'start=0'\
--data-urlencode 'limit=10')
# プロセス検索APIから返ってきたJSONから、ファイルが添付されているプロセスデータだけ切り出す
# tr -d '[:cntrl:]'は制御文字の除去
processes=$(echo $process_search | tr -d '[:cntrl:]' | jq '.processInstances[]' | jq -s)
# forループのために要素数を取得
processes_len=$(echo $processes | jq length)
# 各プロセスについて
for i in $(seq 0 $(($processes_len-1)))
do
# ファイルが添付されているかどうか確認
# 各dataについて、dataTypeがFILE2かつvalueがnullでないもの(ファイルがアップロードされているファイル型項目)を抽出する
data=$(echo $processes | jq .[$i].data[] | jq 'select(.dataType == "FILE2" and .value != null)' | jq -s)
# forループのために要素数を取得
data_len=$(echo $data | jq length)
# 要素数が0
if [ $data_len -eq 0 ] ; then
continue
fi
# プロセス情報からディレクトリ名を作成
process_id=$(echo $processes | jq .[$i].processInstanceId)
# 文字列の前後に付いたダブルクォートをsedで除去
app_name=$(echo $processes | jq .[$i].processModelInfoName | sed -e "s/\"\$//" | sed -e "s/^\"//")
process_title=$(echo $processes | jq .[$i].processInstanceTitle | sed -e "s/\"\$//" | sed -e "s/^\"//")
directory_name=$process_id'_'$process_title'_('$app_name')'
# ディレクトリが存在しなければ作成する
mkdir -p "./file/$directory_name"
# 各ファイル型項目について
for j in $(seq 0 $(($data_len-1)))
do
# アップロードされたファイルの情報を抽出
files=$(echo $data | jq .[$j].value[] | jq -s)
# forループのために要素数を取得
files_len=$(echo $files | jq length)
# 各ファイルについて
for k in $(seq 0 $(($files_len-1)))
do
# ファイルID
file_id=$(echo $files | jq .[$k].id)
# プロセスデータID
process_data_id=$(echo $files | jq .[$k].processDataInstanceId)
# ファイル名
file_name=$(echo $files | jq .[$k].name | sed -e "s/\"\$//" | sed -e "s/^\"//")
# ファイルの保存先
file_path='./file/'$directory_name'/'$file_name
# ファイルダウンロードのためのスクリプト
# > でつないで標準出力をファイル保存(上書き)
file_download=$(curl -u {メールアドレス}:{Basicパスワード}\
'https://example.questetra.net/API/OR/ProcessInstance/File/download'\
--data-urlencode 'id='$file_id\
--data-urlencode 'processDataInstanceId='$process_data_id > $file_path)
echo $file_download
done
done
done
Python コード例
import os
import requests
import json
if __name__ == "__main__":
# APIエンドポイント
process_search_url = 'https://example.questetra.net/API/OR/ProcessInstance/list'
file_download_url = 'https://example.questetra.net/API/OR/ProcessInstance/File/download'
# Basic認証情報
auth = ("{メールアドレス}", "{Basicパスワード}")
# criteriaを読み込み
with open('./process-criteria.txt', 'r') as f:
criteria = f.read()
# プロセス検索のためのパラメータ
search_params = {
'criteria': criteria,
'start': 0,
'limit': 10,
}
try:
# プロセス検索実行
r_search = requests.post(process_search_url, data=search_params, auth=auth) # POST送信
r_search.raise_for_status()
except requests.exceptions.HTTPError as e: # HTTP エラーをキャッチ
print('Error')
print('Status Code: {0}'.format(r_search.status_code))
print(r_search.text)
except requests.exceptions.Timeout as e: # タイムアウトをキャッチ
print('Timeout')
# プロセス検索APIが返したJSONをロード
processes = json.loads(r_search.text)['processInstances']
# 各プロセスについて
for process in processes:
# 各データ項目について
for data in process['data'].values():
# ファイル型でないか、アップロードされたファイルがなければcontinue
if data['dataType'] != 'FILE2' or data['value'] is None:
continue
# ディレクトリ名を作成
directory = './file_py/{0}_{1}_({2})'.format(process['processInstanceId'],
process['processModelInfoName'],
process['processInstanceTitle'])
# ディレクトリが存在しなければ作成
os.makedirs(directory, exist_ok=True)
# 各ファイルについて
for file_info in data['value']:
# ファイルダウンロードのためのパラメータ
download_params = {
'id': file_info['id'],
'processDataInstanceId': file_info['processDataInstanceId']
}
try:
# ファイルダウンロード実行
r_download = requests.post(file_download_url, data=download_params, auth=auth)
r_download.raise_for_status()
except requests.exceptions.HTTPError as e: # HTTP エラーをキャッチ
print('Error')
print('Status Code: {0}'.format(r_search.status_code))
print(r_search.text)
except requests.exceptions.Timeout as e: # タイムアウトをキャッチ
print('Timeout')
# ファイル書込み(バイナリモードでopenすること)
with open(directory + '/' + file_info['name'], 'wb') as f:
f.write(r_download.content)