# 通知設計書 11-業務例外通知

## 概要

本ドキュメントは、RuoYiシステムにおける業務例外通知（ServiceException）の設計について記載する。業務ロジック実行時に発生するビジネスエラーをクライアントに適切に通知するための例外処理機構を定義する。

### 本通知の処理概要

業務例外通知は、サービス層で発生したビジネスロジックエラーをフロントエンドに通知する機構である。ServiceExceptionがスローされると、GlobalExceptionHandlerによりキャッチされ、リクエスト種別に応じた適切なレスポンス形式でエラーメッセージがクライアントに返却される。

**業務上の目的・背景**：システム運用において、ユーザー操作やデータ状態に起因する業務エラー（削除不可、重複登録、権限不足等）を明確にユーザーに伝達することが必要である。技術的なエラーとビジネスエラーを区別し、ユーザーが次のアクションを判断できるようにすることが目的である。

**通知の送信タイミング**：サービス層（Service）の処理において、ビジネスルールに違反する操作が検出された時点でServiceExceptionがスローされ、即座に通知処理が実行される。具体的には、ユーザー削除時の管理者チェック、辞書タイプ削除時の使用中チェック、設定削除時の内蔵パラメータチェック等のタイミングで発生する。

**通知の受信者**：HTTPリクエストを送信したクライアント（ブラウザ、APIクライアント）が受信者となる。Ajaxリクエストの場合はJSONレスポンス、通常リクエストの場合はエラーページへのリダイレクトとして通知される。

**通知内容の概要**：ServiceExceptionに設定されたエラーメッセージがそのまま通知内容となる。例：「不允许操作超级管理员用户」「XXX已分配,不能删除」「部门停用，不允许新增」等、業務コンテキストに応じた具体的なメッセージが返却される。

**期待されるアクション**：ユーザーはエラーメッセージの内容を確認し、操作の修正（別のデータの選択、条件の変更等）または管理者への問い合わせを行う。開発者は例外の詳細ログを確認し、必要に応じてビジネスロジックの改善を検討する。

## 通知種別

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

## 送信仕様

### 基本情報

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

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

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

## 通知テンプレート

### Ajaxリクエストの場合

| 項目 | 内容 |
|-----|------|
| レスポンス形式 | JSON |
| HTTPステータス | 200 OK |

### 本文テンプレート

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

### 通常リクエストの場合

| 項目 | 内容 |
|-----|------|
| レスポンス形式 | HTML |
| 遷移先ページ | error/service.html |

### エラーページテンプレート

```html
<div class="middle-box text-center animated fadeInDown">
    <h3 class="font-bold">操作异常！</h3>
    <div class="error-desc">
        {errorMessage}
    </div>
</div>
```

### 添付ファイル

該当なし

## テンプレート変数

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

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | ユーザー削除操作 | 対象が超級管理者の場合 | SysUserServiceImpl.checkUserAllowed |
| 画面操作 | ユーザーデータ権限チェック | 権限が無い場合 | SysUserServiceImpl.checkUserDataScope |
| 画面操作 | ユーザーインポート | データが空の場合 | SysUserServiceImpl.importUser |
| 画面操作 | 辞書タイプ削除 | 使用中の場合 | SysDictTypeServiceImpl.deleteDictTypeByIds |
| 画面操作 | 設定削除 | 内蔵パラメータの場合 | SysConfigServiceImpl.deleteConfigByIds |
| 画面操作 | 岗位削除 | ユーザー割当済の場合 | SysPostServiceImpl.deletePostByIds |
| 画面操作 | 部門新規登録 | 親部門が停止中の場合 | SysDeptServiceImpl.insertDept |
| 画面操作 | ロール削除 | ユーザー割当済の場合 | SysRoleServiceImpl.deleteRoleByIds |
| 画面操作 | ロール操作 | 対象が超級管理者ロールの場合 | SysRoleServiceImpl.checkRoleAllowed |
| 画面操作 | メニュー並び順保存 | 異常発生時 | SysMenuServiceImpl.updateMenuOrder |
| 画面操作 | コード生成テーブルインポート | 失敗時 | GenTableServiceImpl.importGenTable |
| 画面操作 | コード生成プレビュー | レンダリング失敗時 | GenTableServiceImpl.previewCode |
| 画面操作 | コード生成同期 | 原テーブル不存在時 | GenTableServiceImpl.synchDb |
| 画面操作 | コード生成検証 | ツリー・主子設定不備時 | GenTableServiceImpl.validateEdit |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ServiceException未発生 | ビジネスロジックが正常完了した場合は通知されない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[サービス層で業務エラー検出] --> B[ServiceException生成・スロー]
    B --> C[GlobalExceptionHandler.handleServiceException]
    C --> D{Ajaxリクエスト?}
    D -->|Yes| E[AjaxResult.error作成]
    D -->|No| F[ModelAndView作成]
    E --> G[JSONレスポンス返却]
    F --> H[error/service.htmlへ遷移]
    G --> I[エラーログ記録]
    H --> I
    I --> J[終了]
```

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

### 参照テーブル一覧

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

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | HTTPレスポンス送信エラー | サーバーログに記録、クライアントは接続エラーとして処理 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

- エラーメッセージにはシステム内部情報（スタックトレース、SQL文等）を含めない
- ユーザー操作に関連する情報のみを返却する
- 例外発生時のログにはスタックトレースを含め、デバッグに利用可能とする

## 備考

- ServiceExceptionはRuntimeExceptionを継承しており、チェック例外ではないためtry-catchが強制されない
- detailMessageフィールドで詳細エラー情報を保持可能だが、クライアントには返却されない
- GlobalExceptionHandlerは@RestControllerAdviceにより全コントローラに適用される

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ServiceException.java | `ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java` | 業務例外のデータ構造、message/detailMessageフィールドの役割 |
| 1-2 | AjaxResult.java | `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java` | JSONレスポンスの構造、Type列挙型(SUCCESS=0, WARN=301, ERROR=500) |

**読解のコツ**: ServiceExceptionはfinalクラスであり拡張不可。setMessage/setDetailMessageはBuilderパターン風のメソッドチェーンを可能にしている。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | GlobalExceptionHandler.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java` | @ExceptionHandler(ServiceException.class)によるハンドリング |

**主要処理フロー**:
1. **88-100行目**: handleServiceException メソッド - ServiceExceptionをキャッチ
2. **91行目**: log.error でエラーログ記録
3. **92行目**: ServletUtils.isAjaxRequest でリクエスト種別判定
4. **94行目**: Ajax時 - AjaxResult.error(e.getMessage()) でJSONレスポンス
5. **98行目**: 通常時 - ModelAndView("error/service", "errorMessage", e.getMessage()) でページ遷移

#### Step 3: 例外発生元を理解する

ServiceExceptionがスローされる箇所を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SysUserServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java` | ユーザー関連の業務例外発生箇所 |
| 3-2 | SysDictTypeServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java` | 辞書関連の業務例外発生箇所 |
| 3-3 | SysRoleServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java` | ロール関連の業務例外発生箇所 |

**主要処理フロー**:
- **SysUserServiceImpl 446行目**: `throw new ServiceException("不允许操作超级管理员用户")`
- **SysUserServiceImpl 465行目**: `throw new ServiceException("没有权限访问用户数据！")`
- **SysDictTypeServiceImpl 131行目**: `throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName()))`
- **SysRoleServiceImpl 319行目**: `throw new ServiceException("不允许操作超级管理员角色")`

#### Step 4: エラーページテンプレートを理解する

通常リクエスト時の遷移先ページを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | service.html | `ruoyi-admin/src/main/resources/templates/error/service.html` | エラー表示用HTMLテンプレート |

**主要処理フロー**:
- **16行目**: `[[${errorMessage}]]` - Thymeleafによるエラーメッセージ出力

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

```
Controller (任意のAPI)
    │
    └─ Service層 (SysUserService等)
           │
           ├─ ビジネスロジック検証
           │      └─ throw new ServiceException("エラーメッセージ")
           │
           └─ (例外がスロー)
                  │
                  └─ GlobalExceptionHandler.handleServiceException()
                         │
                         ├─ [Ajax] AjaxResult.error() → JSONレスポンス
                         │
                         └─ [通常] ModelAndView → error/service.html
```

### データフロー図

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

ServiceException ───▶ GlobalExceptionHandler ───▶ AjaxResult (JSON)
(message)                    │                    {"code":500,"msg":"..."}
                             │
                             └─▶ ModelAndView ───▶ error/service.html
                                 (errorMessage)    (HTML表示)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ServiceException.java | `ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java` | ソース | 業務例外クラス定義 |
| 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レスポンス構造体 |
| ServletUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java` | ソース | リクエスト種別判定ユーティリティ |
| service.html | `ruoyi-admin/src/main/resources/templates/error/service.html` | テンプレート | エラー表示ページ |
| SysUserServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java` | ソース | ユーザーサービス（例外発生元） |
| SysDictTypeServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java` | ソース | 辞書タイプサービス（例外発生元） |
| SysRoleServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java` | ソース | ロールサービス（例外発生元） |
| SysDeptServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDeptServiceImpl.java` | ソース | 部門サービス（例外発生元） |
| GenTableServiceImpl.java | `ruoyi-generator/src/main/java/com/ruoyi/generator/service/impl/GenTableServiceImpl.java` | ソース | コード生成サービス（例外発生元） |
