バックグラウンドサーバースクリプトの仕組み
バックグラウンドサーバースクリプト(テナント管理から登録し、スケジュールで動かすサーバースクリプト)は、プリザンターに組み込まれた Quartz.NET のスケジューラの上で動いています。スケジュールの画面設定は内部で Quartz の cron 式に変換され、発火すると HTTP リクエストの無い専用の Context でサーバースクリプトが実行されます。
使い方の例は 拡張サーバースクリプトとバックグラウンドサーバースクリプト を参照してください。
全体像
図を読み込み中…
- アプリケーションの起動時、ウォームアップの完了を待ってから、全テナントのスクリプトをスケジューラに登録し、スケジューラを開始します(CustomQuartzHostedService.cs#L33-L68、BackgroundServerScriptUtilities.cs#L31-L51)。
- テナント管理で設定を更新すると、そのテナントのジョブ(グループ
BGServerScript_TenantId_{テナントID})を全部消してから登録し直します(TenantModel.cs#L2259-L2264、BackgroundServerScriptUtilities.cs#L53-L85)。 - 登録されるのは「無効」でなく、「共有」でなく、スケジュールが 1 つ以上あるスクリプトです。スクリプト 1 つが 1 ジョブ、スケジュール 1 つが 1 トリガーになります。
- 次回の発火時刻が無いトリガー(過去の日時を指定した「一回のみ」など)は登録されません(BackgroundServerScriptUtilities.cs#L82)。エラーにもならないので、登録したのに動かないときは日時を確認してください。
有効にする条件
スケジュールの登録とジョブの実行のたびに、次の 3 つがすべて満たされているかを確認します(BackgroundService.cs#L57-L67)。
| 条件 | パラメータ | 既定値 |
|---|---|---|
| サーバースクリプトが有効 | Script.json の ServerScript | true |
| バックグラウンドサーバースクリプトが有効 | Script.json の BackgroundServerScript | false |
| 実行環境が一致する | BackgroundService.json の EnvironmentVariables と Service.json の DeploymentEnvironment | EnvironmentVariables が null(制限なし) |
EnvironmentVariables を使うと、複数台構成のうち特定の環境だけでバックグラウンド処理を動かせます。
スケジューラの構成
Quartz.json の Clustering.Enabled で、スケジューラの作り方が変わります(CustomQuartzHostedService.cs#L20-L31、#L101-L152)。
| 項目 | クラスタリングなし(既定) | クラスタリングあり |
|---|---|---|
| スケジューラ | StdSchedulerFactory.GetDefaultScheduler() | 設定値から作成 |
| ジョブストア | Quartz 既定(メモリ上) | AdoJobStore(プリザンターの DB に QRTZ_ 始まりのテーブル) |
| 同時実行数 | Quartz 既定 | MaxConcurrency(既定 10) |
| チェックイン間隔 | なし | CheckinInterval(既定 15000 ミリ秒) |
| ミスファイア閾値 | なし | MaxMisfireThreshold(既定 60000 ミリ秒) |
ジョブの基底クラス ClusterExecutionTimerBase には [DisallowConcurrentExecution] が付いているため、同じジョブ(同じスクリプト)が前回の実行中に重なって動くことはありません(ClusterExecutionTimerBase.cs#L5)。1.5.8.1 は Quartz.AspNetCore 3.19.1 を参照しています(Implem.Pleasanter.csproj#L80)。
スケジュールと cron 式
スケジュールの画面設定(BackgroundSchedule)は、GetTrigger() で Quartz の 7 フィールドの cron 式(秒 分 時 日 月 曜日 年)に変換されます(BackgroundServerScriptUtilities.cs#L112-L179)。
種別(ScheduleType) | 画面の入力 | 生成される cron 式 | 例 |
|---|---|---|---|
hourly(毎時) | 分(00〜55 の 5 分刻みのドロップダウン) | 0 {分} * * * ? * | 毎時 30 分 → 0 30 * * * ? * |
daily(毎日) | 時刻(HH:mm) | 0 {分} {時} * * ? * | 9:00 → 0 0 9 * * ? * |
weekly(毎週) | 曜日(複数選択)と時刻 | 0 {分} {時} ? * {曜日} * | 月水金 9:00 → 0 0 9 ? * 2,4,6 * |
monthly(毎月) | 月(複数)・日(複数)と時刻 | 0 {分} {時} {日} {月} ? * | 毎月 1 日と 15 日 9:00 → 0 0 9 1,15 * ? * |
onlyonce(一回のみ) | 日時 | 0 {分} {時} {日} {月} ? {年} | 2026/3/15 9:00 → 0 0 9 15 3 ? 2026 |
- 曜日の値は Quartz と同じく 1 が日曜、7 が土曜です(TenantUtilities.cs#L2598-L2610)。
- 日の選択肢の「月末」は値が
32で、cron 式ではLになります(TenantUtilities.cs#L2672、BackgroundServerScriptUtilities.cs#L196-L202)。 - タイムゾーンは、スケジュールのタイムゾーン →
Service.jsonのTimeZoneDefault→ UTC の順に決まります。 - 保存時のサーバー側の検証は、毎日・毎週・毎月の時刻が
HH:mm形式かどうかだけです。曜日・月・日の値は検証されません(BackgroundScheduleValidators.cs#L22-L39)。値が解釈できないスケジュールはトリガーが作られず、黙って登録されません。
このため、画面からは「15 分ごと」「平日の 9〜17 時だけ」「第 2 火曜日」のような指定はできません。毎時の分も 5 分刻みで、複数の時刻に動かすにはスケジュールを複数登録します。cron 式を直接入力できるようにする改修案は バックグラウンドサーバースクリプトに cron 式を直接指定する にまとめています。
実行の流れ
図を読み込み中…
(BackgroundServerScriptJob.cs#L20-L91)
- 実行するスクリプトは、同じテナントの「共有」のバックグラウンドサーバースクリプト全部と、ジョブの対象スクリプト 1 つです。共有スクリプトはスケジュールでは動かず、他のスクリプトに結合される部品として使われます。
- スケジュール実行ではスクリプトを毎回 DB(
Tenants.TenantSettings)から読み直します。 - ログは SysLogs に
Exec BGServerScript TenantId=…,ScriptId=…,ScheduleId=…,UserId=…(実行時)またはSkip BGServerScript …(対象が無い・実行ユーザーが使えないとき)として記録され、処理の終わりにFinishされます。例外も SysLogs に記録されます。 - タイムアウトは通常のサーバースクリプトと同じく、スクリプトの
TimeOutとScript.jsonのServerScriptTimeOutから決まります(タイムアウト)。
即時実行
スクリプトの編集ダイアログから即時実行すると、保存前のダイアログの内容をジョブのデータとして渡し、StartNow() のトリガーで 1 回だけ実行します(TenantModel.cs#L1941-L1964、BackgroundServerScriptUtilities.cs#L87-L110)。実行ユーザーが未入力のとき、「共有」にチェックがあるときは実行されません。スクリプトの「デバッグ」指定が効くのはこの即時実行のときだけで、スケジュール実行では常にデバッグなしで動きます(BackgroundServerScriptJob.cs#L77)。
実行時の Context
HTTP リクエストが無いため、テナント ID・実行ユーザーの ID・そのユーザーの部署 ID から Context を作ります(request: false、setAuthenticated: true)。context.BackgroundServerScript が true になり、AbsoluteUri には Service.json の AbsoluteUri が入り、タイムゾーンは実行ユーザーの設定になります(BackgroundServerScriptJob.cs#L110-L133)。
スクリプトの中で items.Get などを呼ぶときの内部の Context も、API リクエストの本文をマージせず、同じユーザー・部署・テナントから作り直し、タイムゾーンを引き継いで権限を設定し直します(ServerScriptUtilities.cs#L1401-L1435)。つまり、スクリプトは実行ユーザーの権限で動きます。
安全のための制御
| 制御 | 内容 |
|---|---|
| 登録できる人 | テナント管理の画面から登録する |
| 実行ユーザーの確認 | 実行ユーザーがそのテナントに存在し、無効化されていないこと。保存時と実行時の両方で確認する(BackgroundServerScriptValidators.cs#L29-L47) |
| 機能ごと無効化 | Script.json の BackgroundServerScript(既定 false) |
httpClient の無効化 | Script.json の DisableServerScriptHttpClient が true なら httpClient 自体を渡さない(ServerScriptUtilities.cs#L1240-L1243) |
$ps.file の無効化 | Script.json の DisableServerScriptFile(既定 true) |
| 多重実行の防止 | [DisallowConcurrentExecution] |
httpClient には接続先の制限(localhost や社内ネットワーク宛てを拒否するなど)はありません(ServerScriptModelHttpClient.cs#L84)。バックグラウンドサーバースクリプトから社内のサービスにも到達できるので、必要ならネットワーク側で制限してください。
C# のプログラムを定期実行したいとき
バックグラウンドサーバースクリプトで動かせるのは JavaScript だけです。C# で書いた処理を定期実行したい場合、本体を改修しない方法は、処理を別プロセスの Web API として立て、バックグラウンドサーバースクリプトの httpClient から呼ぶ構成です。本体に DLL を読み込ませる改修案との比較は バックグラウンドで C# の DLL を実行する にまとめています。