# 通知設計書 12-システム例外通知

## 概要

本ドキュメントは、RuoYiシステムにおけるシステム例外通知（RuntimeException/Exception）の設計について記載する。予期しない実行時エラーやシステムエラーが発生した際に、クライアントへ適切なエラーメッセージを返却する例外処理機構を定義する。

### 本通知の処理概要

システム例外通知は、アプリケーション全体で発生する未知のランタイム例外および一般例外をキャッチし、クライアントにエラー情報を通知する機構である。GlobalExceptionHandlerにより、RuntimeExceptionとExceptionがそれぞれ個別にハンドリングされ、エラーメッセージとHTTPステータスコード500がJSONレスポンスとして返却される。同時に、サーバーログにエラー詳細（スタックトレース含む）が記録される。

**業務上の目的・背景**：システム運用において、予期しないエラー（NullPointerException、データベース接続エラー、外部API呼び出し失敗等）が発生した場合でも、システムが適切にエラーをハンドリングし、ユーザーにフィードバックを提供することが必要である。また、運用担当者がエラーの原因を追跡できるようにログ記録を行うことも重要である。

**通知の送信タイミング**：コントローラまたはサービス層の処理中にRuntimeExceptionまたはExceptionがスローされ、かつ他のより具体的な例外ハンドラー（ServiceException、DemoModeException、AuthorizationException等）でキャッチされなかった場合に、このハンドラーが動作する。

**通知の受信者**：HTTPリクエストを送信したクライアント（ブラウザ、APIクライアント）が受信者となる。すべてJSONレスポンスとして返却される。

**通知内容の概要**：例外のメッセージ（Exception.getMessage()）がエラーメッセージとして返却される。技術的な詳細（スタックトレース等）はセキュリティ上の理由からクライアントには返却せず、サーバーログにのみ記録される。

**期待されるアクション**：ユーザーは操作を再試行するか、システム管理者に連絡する。運用担当者・開発者はサーバーログを確認し、エラーの根本原因を調査・修正する。

## 通知種別

アプリ内通知（HTTPレスポンス）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期 |
| 優先度 | 高 |
| リトライ | 無 |

### 送信先決定ロジック

HTTPリクエストを送信したクライアントに対して直接レスポンスとして返却する。送信先の決定はHTTPプロトコルにより自動的に行われる。

## 通知テンプレート

### JSONレスポンス

| 項目 | 内容 |
|-----|------|
| レスポンス形式 | JSON |
| HTTPステータス | 200 OK（実際のエラーコードはJSONボディ内） |

### 本文テンプレート

```json
{
  "code": 500,
  "msg": "{Exception.message}"
}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| msg | エラーメッセージ | Exception.getMessage() | Yes |
| code | ステータスコード | AjaxResult.Type.ERROR (500) | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| システム全般 | RuntimeException発生 | 他のハンドラーでキャッチされない場合 | handleRuntimeException |
| システム全般 | Exception発生 | 他のハンドラーでキャッチされない場合 | handleException |
| API呼び出し | HTTPメソッド不サポート | GET/POST等が一致しない場合 | handleHttpRequestMethodNotSupported |
| API呼び出し | パス変数不足 | URLパスパラメータ欠損 | handleMissingPathVariableException |
| API呼び出し | パラメータ型不一致 | リクエストパラメータの型変換失敗 | handleMethodArgumentTypeMismatchException |
| バリデーション | バインドエラー | フォームバリデーション失敗 | handleBindException |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 具体的例外ハンドラーの存在 | ServiceException、DemoModeException、AuthorizationException等は専用ハンドラーで処理される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[例外発生] --> B{例外タイプ判定}
    B -->|ServiceException| C[handleServiceException]
    B -->|DemoModeException| D[handleDemoModeException]
    B -->|AuthorizationException| E[handleAuthorizationException]
    B -->|RuntimeException| F[handleRuntimeException]
    B -->|Exception| G[handleException]
    F --> H[エラーログ記録]
    G --> H
    H --> I[AjaxResult.error作成]
    I --> J[JSONレスポンス返却]
    J --> K[終了]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（例外処理時にはDBアクセスは行われない）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 未知のRuntimeException | 予期しない実行時エラー | ログ記録し、汎用エラーメッセージを返却 |
| 未知のException | チェック例外がキャッチされずにスロー | ログ記録し、汎用エラーメッセージを返却 |
| HTTPメソッド不サポート | 誤ったHTTPメソッドでのアクセス | 具体的なエラーメッセージを返却 |
| パス変数不足 | URL構造の誤り | 必須パス変数名を含むエラーメッセージを返却 |
| パラメータ型不一致 | 型変換失敗 | 期待する型と実際の入力値を含むエラーメッセージを返却 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（同期処理のため） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（システム稼働中は常時）

## セキュリティ考慮事項

- スタックトレースはクライアントに返却しない（サーバーログにのみ記録）
- データベースエラーの詳細（SQL文、テーブル名等）はクライアントに露出しない
- Exception.getMessage()の内容によってはセンシティブ情報が漏洩する可能性があるため、プロダクション環境では汎用メッセージへの置換を検討
- XSS対策としてEscapeUtil.clean()によるパラメータ値のサニタイズを実施（handleMethodArgumentTypeMismatchException）

## 備考

- RuntimeExceptionハンドラーとExceptionハンドラーは同様の処理を行うが、ログメッセージが異なる（「発生未知异常」vs「発生系统异常」）
- @RestControllerAdviceによりすべてのコントローラに自動適用
- 例外のハンドリング順序は具体的なクラス→一般的なクラスの順となり、より具体的なハンドラーが優先される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、レスポンスクラスの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AjaxResult.java | `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java` | JSONレスポンスの構造、error()メソッドの実装 |

**読解のコツ**: AjaxResultはHashMapを継承しており、"code"、"msg"、"data"をキーとしてデータを保持する。

#### Step 2: エントリーポイントを理解する

例外ハンドラーの処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | GlobalExceptionHandler.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java` | 各例外タイプのハンドリングメソッド |

**主要処理フロー**:

**RuntimeExceptionハンドラー (66-72行目)**:
1. **66行目**: `@ExceptionHandler(RuntimeException.class)` - RuntimeExceptionをキャッチ
2. **69行目**: `String requestURI = request.getRequestURI()` - リクエストURI取得
3. **70行目**: `log.error("请求地址'{}',发生未知异常.", requestURI, e)` - エラーログ記録（スタックトレース含む）
4. **71行目**: `return AjaxResult.error(e.getMessage())` - エラーレスポンス返却

**Exceptionハンドラー (77-83行目)**:
1. **77行目**: `@ExceptionHandler(Exception.class)` - Exceptionをキャッチ
2. **80行目**: `String requestURI = request.getRequestURI()` - リクエストURI取得
3. **81行目**: `log.error("请求地址'{}',发生系统异常.", requestURI, e)` - エラーログ記録
4. **82行目**: `return AjaxResult.error(e.getMessage())` - エラーレスポンス返却

**HTTPメソッド不サポートハンドラー (54-61行目)**:
1. **54行目**: `@ExceptionHandler(HttpRequestMethodNotSupportedException.class)`
2. **59行目**: `log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod())` - サポートされないメソッドをログ記録

**パス変数不足ハンドラー (105-111行目)**:
1. **109行目**: `log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e)`
2. **110行目**: `return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()))`

**パラメータ型不一致ハンドラー (116-128行目)**:
1. **121行目**: `String value = Convert.toStr(e.getValue())` - 値を文字列に変換
2. **124行目**: `value = EscapeUtil.clean(value)` - XSS対策としてサニタイズ
3. **127行目**: 詳細なエラーメッセージを生成

#### Step 3: ユーティリティクラスを理解する

サニタイズ処理等を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | EscapeUtil.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java` | HTMLエスケープ処理 |
| 3-2 | Convert.java | `ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java` | 型変換ユーティリティ |

### プログラム呼び出し階層図

```
Controller (任意のAPI)
    │
    └─ Service/Repository層
           │
           └─ RuntimeException/Exception発生
                  │
                  └─ GlobalExceptionHandler
                         │
                         ├─ handleRuntimeException()  [RuntimeException]
                         │      │
                         │      ├─ log.error() - ログ記録
                         │      │
                         │      └─ AjaxResult.error() - レスポンス生成
                         │
                         ├─ handleException()  [Exception]
                         │      │
                         │      ├─ log.error() - ログ記録
                         │      │
                         │      └─ AjaxResult.error() - レスポンス生成
                         │
                         ├─ handleHttpRequestMethodNotSupported()
                         │
                         ├─ handleMissingPathVariableException()
                         │
                         ├─ handleMethodArgumentTypeMismatchException()
                         │      │
                         │      └─ EscapeUtil.clean() - XSS対策
                         │
                         └─ handleBindException()
```

### データフロー図

```
[入力]                    [処理]                              [出力]

RuntimeException ────────▶ handleRuntimeException ──────────▶ AjaxResult (JSON)
(message)                        │                           {"code":500,"msg":"..."}
                                 │
                                 └─▶ log.error() ──────────▶ サーバーログ
                                     (スタックトレース)

Exception ───────────────▶ handleException ────────────────▶ AjaxResult (JSON)
(message)                        │                           {"code":500,"msg":"..."}
                                 │
                                 └─▶ log.error() ──────────▶ サーバーログ
                                     (スタックトレース)

HttpRequestMethodNotSupportedException
                    ─────▶ handleHttpRequestMethodNotSupported ▶ AjaxResult (JSON)
                                                                {"code":500,"msg":"..."}
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| GlobalExceptionHandler.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java` | ソース | 全体例外ハンドラー |
| AjaxResult.java | `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java` | ソース | Ajaxレスポンス構造体 |
| Convert.java | `ruoyi-common/src/main/java/com/ruoyi/common/core/text/Convert.java` | ソース | 型変換ユーティリティ |
| EscapeUtil.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java` | ソース | HTMLエスケープユーティリティ |
| StringUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/StringUtils.java` | ソース | 文字列ユーティリティ |
