# 通知設計書 8-操作ログ記録通知

## 概要

本ドキュメントは、RuoYiシステムにおける操作ログ記録通知機能の設計仕様を記述したものである。@Logアノテーションが付与されたコントローラメソッドの操作内容を非同期でデータベースに記録する機能について定義する。

### 本通知の処理概要

本通知は、システム内の重要な操作（新規登録、更新、削除等）が実行された際に、操作内容を監査ログとして非同期でデータベースに記録する機能を提供する。AOP（アスペクト指向プログラミング）を使用し、@Logアノテーションが付与されたメソッドの実行前後で自動的にログを記録する。

**業務上の目的・背景**：セキュリティ監査、コンプライアンス対応、問題発生時の調査のため、システム内の重要な操作履歴を記録する必要がある。誰が、いつ、どのような操作を行ったかを追跡可能にし、不正操作の検知や変更履歴の管理に活用される。

**通知の送信タイミング**：@Logアノテーションが付与されたコントローラメソッドの実行完了後（正常終了・例外発生両方）に非同期タスクとしてログ記録が実行される。LogAspectのAOP処理でトリガーされる。

**通知の受信者**：システム管理者（操作ログ閲覧権限を持つユーザー）。記録されたログは監視画面（/monitor/operlog）から参照可能。

**通知内容の概要**：操作モジュール名、業務タイプ、操作者、操作URL、操作IPアドレス、操作地点、リクエストパラメータ、レスポンス結果、操作ステータス（成功/失敗）、エラーメッセージ、実行時間などが記録される。

**期待されるアクション**：管理者は操作ログを監視し、不審な操作パターン（大量削除、権限外操作等）を検知する。問題発生時の原因調査、監査対応、操作統計の取得などに活用する。

## 通知種別

ログ記録（データベース監査ログ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（AsyncManager経由） |
| 優先度 | 中 |
| リトライ | 無（非同期タスクのため） |

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

- ログは一律でsys_oper_logテーブルに記録
- @Logアノテーションが付与されたメソッドのみ対象

## 通知テンプレート

### ログ記録の場合

| 項目 | 内容 |
|-----|------|
| 記録先 | sys_oper_logテーブル |
| 記録形式 | データベースレコード |

### 本文テンプレート

```
操作モジュール: {title}
業務タイプ: {businessType}
メソッド名: {method}
リクエスト方式: {requestMethod}
操作者種別: {operatorType}
操作者: {operName}
部門名: {deptName}
リクエストURL: {operUrl}
操作IP: {operIp}
操作地点: {operLocation}
リクエストパラメータ: {operParam}
返却値: {jsonResult}
操作ステータス: {status}
エラーメッセージ: {errorMsg}
操作時間: {operTime}
実行時間: {costTime}ms
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| - | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| title | 操作モジュール名 | @Log.title() | Yes |
| businessType | 業務タイプ | @Log.businessType() | Yes |
| method | メソッド名 | クラス名.メソッド名() | Yes |
| requestMethod | リクエスト方式 | request.getMethod() | Yes |
| operatorType | 操作者種別 | @Log.operatorType() | No |
| operName | 操作者名 | ShiroUtils.getSysUser().getLoginName() | No |
| deptName | 部門名 | ShiroUtils.getSysUser().getDept().getDeptName() | No |
| operUrl | リクエストURL | request.getRequestURI() | Yes |
| operIp | 操作IP | ShiroUtils.getIp() | Yes |
| operLocation | 操作地点 | AddressUtils.getRealAddressByIP() | No |
| operParam | リクエストパラメータ | request.getParameterMap() / args | No |
| jsonResult | 返却値 | メソッド戻り値 | No |
| status | 操作ステータス | 0=成功, 1=失敗 | Yes |
| errorMsg | エラーメッセージ | 例外メッセージ | No |
| operTime | 操作日時 | 現在日時 | Yes |
| costTime | 実行時間(ms) | 終了時刻-開始時刻 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| AOP | メソッド正常終了 | @Logアノテーション付与 | @AfterReturning |
| AOP | メソッド例外発生 | @Logアノテーション付与 | @AfterThrowing |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| @Logアノテーションなし | アノテーションがないメソッドは対象外 |
| isSaveRequestData=false | リクエストパラメータを記録しない |
| isSaveResponseData=false | レスポンスを記録しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[コントローラメソッド呼び出し] --> B[@Before: 開始時刻記録]
    B --> C[メソッド実行]
    C --> D{例外発生?}
    D -->|No| E[@AfterReturning]
    D -->|Yes| F[@AfterThrowing]
    E --> G[handleLog]
    F --> G
    G --> H[SysOperLog構築]
    H --> I[操作者情報取得]
    I --> J[エラー情報設定]
    J --> K[メソッド情報設定]
    K --> L[パラメータ/結果設定]
    L --> M[実行時間計算]
    M --> N[AsyncManager.execute]
    N --> O[AsyncFactory.recordOper]
    O --> P[IP逆引き]
    P --> Q[sys_oper_logにINSERT]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| - | - | セッションから取得 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| sys_oper_log | INSERT | 操作ログ記録 |

#### sys_oper_log テーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | oper_id | 自動採番 | 主キー |
| INSERT | title | モジュール名 | @Log.title() |
| INSERT | business_type | 業務タイプ | 0-9の数値 |
| INSERT | method | メソッド名 | - |
| INSERT | request_method | GET/POST等 | - |
| INSERT | operator_type | 操作者種別 | 0=その他,1=管理者,2=モバイル |
| INSERT | oper_name | 操作者名 | - |
| INSERT | dept_name | 部門名 | - |
| INSERT | oper_url | URL | 最大255文字 |
| INSERT | oper_ip | IPアドレス | - |
| INSERT | oper_location | 操作地点 | IP逆引き |
| INSERT | oper_param | パラメータ | 最大2000文字 |
| INSERT | json_result | 戻り値 | 最大2000文字 |
| INSERT | status | ステータス | 0=成功,1=失敗 |
| INSERT | error_msg | エラーメッセージ | 最大2000文字 |
| INSERT | oper_time | 操作日時 | - |
| INSERT | cost_time | 実行時間 | ミリ秒 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ユーザー情報取得失敗 | 未認証状態 | operName等を空で記録 |
| パラメータ変換失敗 | 複雑なオブジェクト | エラーログ出力、記録スキップ |
| DB接続エラー | データベース接続不可 | エラーログ出力のみ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（非同期タスクのため） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

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

### 配信時間帯

制限なし。操作に応じて常時記録。

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

- **パスワードマスク**：password, oldPassword, newPassword, confirmPasswordはログから除外
- **excludeParamNames**：@Logアノテーションで除外パラメータを指定可能
- **パラメータ長制限**：operParam, jsonResult, errorMsgは最大2000文字に切り詰め
- **機密情報フィルタ**：MultipartFile, HttpServletRequest等はログ対象外

## 備考

- 業務タイプ: 0=その他, 1=新規, 2=更新, 3=削除, 4=認可, 5=エクスポート, 6=インポート, 7=強制退出, 8=コード生成, 9=データクリア
- 操作者種別: 0=その他, 1=管理者, 2=モバイルユーザー
- ThreadLocalで開始時刻を保持し、実行時間を計算
- finallyブロックでThreadLocalをクリア

---

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

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

### 推奨読解順序

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

操作ログに使用されるエンティティクラスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SysOperLog.java | `ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java` | 操作ログエンティティの構造 |
| 1-2 | Log.java | `ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java` | @Logアノテーション定義 |

**読解のコツ**: businessType, operatorTypeの数値と意味の対応を理解する。@ExcelアノテーションでExcel出力時の変換定義を確認。

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

AOPアスペクトを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | LogAspect.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java` | AOP処理全体 |

**主要処理フロー**:
1. **45行目**: 除外パラメータ定義 - `EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }`
2. **48行目**: 開始時刻保持用ThreadLocal
3. **56-60行目**: @Before - 開始時刻記録
4. **67-71行目**: @AfterReturning - 正常終了時処理
5. **79-83行目**: @AfterThrowing - 例外発生時処理
6. **85-137行目**: handleLog() - ログ記録メイン処理
7. **93-94行目**: SysOperLog生成、初期ステータス設定
8. **96-107行目**: 操作者情報設定
9. **109-113行目**: 例外情報設定
10. **114-119行目**: メソッド情報設定
11. **121行目**: アノテーション情報取得
12. **123行目**: 実行時間計算
13. **125行目**: **非同期ログ記録** - `AsyncManager.me().execute(AsyncFactory.recordOper(operLog))`

#### Step 3: 非同期ログ記録処理を理解する

非同期タスク生成を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AsyncFactory.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java` | recordOper()メソッド |

**主要処理フロー**:
- **69-81行目**: recordOper() - 操作ログ記録タスク
- **77行目**: IP逆引き
- **78行目**: DB登録

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

```
Controllerメソッド (@Log付与)
    │
    └─ LogAspect (AOP)
           │
           ├─ @Before: doBefore()
           │      └─ TIME_THREADLOCAL.set(開始時刻)
           │
           ├─ メソッド実行
           │
           ├─ @AfterReturning / @AfterThrowing
           │      └─ handleLog()
           │             │
           │             ├─ SysOperLog構築
           │             │
           │             ├─ ShiroUtils.getSysUser() (操作者情報)
           │             │
           │             ├─ getControllerMethodDescription() (アノテーション情報)
           │             │
           │             └─ AsyncManager.me().execute()
           │                    │
           │                    └─ AsyncFactory.recordOper()
           │                           │
           │                           ├─ AddressUtils.getRealAddressByIP()
           │                           │
           │                           └─ ISysOperLogService.insertOperlog()
           │                                  │
           │                                  └─ sys_oper_log INSERT
           │
           └─ finally: TIME_THREADLOCAL.remove()
```

### データフロー図

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

HTTPリクエスト ───▶ Controller (@Log) ───▶ HTTPレスポンス
    │                    │
    │                    ▼
    │               LogAspect (AOP)
    │                    │
    │                    ▼
    │              SysOperLog構築
    │                    │
    │                    ▼
    │              AsyncManager (非同期)
    │                    │
    │                    ▼
    └─────────────▶ sys_oper_log テーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| LogAspect.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java` | ソース | AOPアスペクト |
| Log.java | `ruoyi-common/src/main/java/com/ruoyi/common/annotation/Log.java` | ソース | @Logアノテーション |
| SysOperLog.java | `ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOperLog.java` | ソース | 操作ログエンティティ |
| AsyncFactory.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java` | ソース | 非同期タスクファクトリ |
| ISysOperLogService.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOperLogService.java` | ソース | 操作ログサービス |
| BusinessType.java | `ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessType.java` | ソース | 業務タイプ列挙 |
| OperatorType.java | `ruoyi-common/src/main/java/com/ruoyi/common/enums/OperatorType.java` | ソース | 操作者タイプ列挙 |
| BusinessStatus.java | `ruoyi-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java` | ソース | 処理ステータス列挙 |
