# 機能設計書 22-プロファイル管理

## 概要

本ドキュメントは、RuoYiシステムにおけるプロファイル管理機能の詳細設計を記述する。ログインユーザーが自身の個人情報を表示・編集し、パスワード変更やアバター画像の更新を行うための機能を提供する。

### 本機能の処理概要

本機能は、認証済みユーザーが自身のプロファイル情報を管理するためのセルフサービス機能を提供する。ユーザー名、メールアドレス、電話番号、性別などの基本情報の更新、パスワードの変更、プロファイル画像（アバター）のアップロード・更新が可能である。

**業務上の目的・背景**：システム利用者が管理者に依頼することなく、自身の連絡先情報やパスワードを自律的に管理できるようにすることで、管理者の負担を軽減し、ユーザーの利便性を向上させる。また、定期的なパスワード変更を促進し、セキュリティレベルを維持することを目的とする。

**機能の利用シーン**：
- ユーザーが連絡先情報（メールアドレス、電話番号）を変更したい場面
- 定期的なパスワード変更ポリシーに従ってパスワードを更新する場面
- プロファイル画像を設定・変更してシステム内での識別性を高める場面
- 自身に割り当てられているロールや役職を確認する場面

**主要な処理内容**：
1. ログインユーザーの個人情報表示（ロールグループ、役職グループを含む）
2. 個人情報（ユーザー名、メール、電話番号、性別）の編集と保存
3. 旧パスワード検証を伴うパスワード変更処理
4. アバター画像のアップロードと旧画像の削除
5. パスワード一致確認のためのリアルタイムチェック

**関連システム・外部連携**：
- ファイルシステム（アバター画像の保存・削除）
- Shiroセキュリティフレームワーク（パスワード暗号化、セッション管理）

**権限による制御**：本機能はログイン済みユーザーのみがアクセス可能。自身の情報のみ編集可能であり、他ユーザーの情報にはアクセスできない。管理者権限は不要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | プロフィール画面 | 主画面 | 個人情報の表示処理 |
| 17 | アバター変更 | 主画面 | アバター画像のアップロード・変更処理 |
| 18 | パスワード変更 | 主画面 | 自身のパスワード変更処理 |

## 機能種別

CRUD操作（Read/Update） / ファイルアップロード / パスワード管理

## 入力仕様

### 入力パラメータ

#### プロファイル表示（GET /system/user/profile）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | パラメータなし（セッションからユーザー情報取得） | - |

#### パスワード確認（GET /system/user/profile/checkPassword）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| password | String | Yes | 確認対象のパスワード | - |

#### パスワード変更（POST /system/user/profile/resetPwd）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| oldPassword | String | Yes | 現在のパスワード | - |
| newPassword | String | Yes | 新しいパスワード | 旧パスワードと異なること |

#### 個人情報更新（POST /system/user/profile/update）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| userName | String | No | ユーザー名 | - |
| email | String | No | メールアドレス | 一意性チェック |
| phonenumber | String | No | 電話番号 | 一意性チェック |
| sex | String | No | 性別（0:男性、1:女性） | - |

#### アバター更新（POST /system/user/profile/updateAvatar）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| avatarfile | MultipartFile | Yes | アバター画像ファイル | 画像形式（bmp,gif,jpg,jpeg,png） |

### 入力データソース

- HTTPセッション（ログインユーザー情報）
- HTTPリクエストパラメータ（フォームデータ）
- マルチパートファイル（アバター画像）

## 出力仕様

### 出力データ

#### プロファイル表示

| 項目名 | 型 | 説明 |
|--------|-----|------|
| user | SysUser | ユーザー情報オブジェクト |
| roleGroup | String | ロール名のカンマ区切り文字列 |
| postGroup | String | 役職名のカンマ区切り文字列 |

#### パスワード確認

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | boolean | パスワード一致結果（true/false） |

#### 各更新処理

| 項目名 | 型 | 説明 |
|--------|-----|------|
| code | int | 処理結果コード（0:成功、その他:エラー） |
| msg | String | メッセージ |

### 出力先

- 画面表示（Thymeleafテンプレート）
- JSONレスポンス（Ajax応答）
- ファイルシステム（アバター画像保存）

## 処理フロー

### 処理シーケンス

```
【プロファイル表示】
1. セッションからログインユーザー取得
   └─ getSysUser()
2. ロールグループ取得
   └─ userService.selectUserRoleGroup(userId)
3. 役職グループ取得
   └─ userService.selectUserPostGroup(userId)
4. ModelMapにデータ設定
   └─ user, roleGroup, postGroup
5. ビュー返却
   └─ system/user/profile/profile

【パスワード変更】
1. セッションからログインユーザー取得
2. 旧パスワード照合
   └─ passwordService.matches(user, oldPassword)
3. 新旧パスワード同一チェック
   └─ passwordService.matches(user, newPassword)
4. ソルト生成
   └─ ShiroUtils.randomSalt()
5. パスワード暗号化
   └─ passwordService.encryptPassword(loginName, newPassword, salt)
6. DB更新
   └─ userService.resetUserPwd(user)
7. セッション更新
   └─ setSysUser()

【アバター更新】
1. ファイル空チェック
2. セッションからログインユーザー取得
3. ファイルアップロード
   └─ FileUploadUtils.upload(avatarPath, file, IMAGE_EXTENSION, true)
4. DB更新
   └─ userService.updateUserAvatar(userId, avatar)
5. 旧アバター削除
   └─ FileUtils.deleteFile()
6. セッション更新
   └─ setSysUser()
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{操作種別}
    B -->|プロファイル表示| C[セッションからユーザー取得]
    C --> D[ロールグループ取得]
    D --> E[役職グループ取得]
    E --> F[画面表示]

    B -->|パスワード変更| G[旧パスワード照合]
    G --> H{一致?}
    H -->|No| I[エラー: 旧パスワードが違う]
    H -->|Yes| J{新旧同一?}
    J -->|Yes| K[エラー: 同一パスワード]
    J -->|No| L[ソルト生成・暗号化]
    L --> M[DB更新]
    M --> N[セッション更新]
    N --> O[成功]

    B -->|アバター更新| P[ファイル空チェック]
    P --> Q{空?}
    Q -->|Yes| R[エラー]
    Q -->|No| S[ファイルアップロード]
    S --> T[DB更新]
    T --> U[旧アバター削除]
    U --> V[セッション更新]
    V --> O

    F --> W[終了]
    I --> W
    K --> W
    R --> W
    O --> W
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 自己情報のみ編集 | ユーザーは自身のプロファイルのみ編集可能 | 常時 |
| BR-02 | 旧パスワード必須 | パスワード変更時は現在のパスワードを正しく入力する必要がある | パスワード変更時 |
| BR-03 | 新旧パスワード相違 | 新しいパスワードは現在のパスワードと異なる必要がある | パスワード変更時 |
| BR-04 | メール一意性 | メールアドレスはシステム内で一意である必要がある | 個人情報更新時 |
| BR-05 | 電話番号一意性 | 電話番号はシステム内で一意である必要がある | 個人情報更新時 |
| BR-06 | 画像形式制限 | アバターはbmp,gif,jpg,jpeg,png形式のみ許可 | アバター更新時 |
| BR-07 | 旧アバター削除 | 新アバター登録時に旧アバターファイルを削除 | アバター更新時 |

### 計算ロジック

**パスワード暗号化ロジック**：
```
暗号化パスワード = MD5(loginName + password + salt).toHex()
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| プロファイル表示 | sys_user | SELECT | ユーザー情報取得 |
| プロファイル表示 | sys_role | SELECT | ロール情報取得（ロールグループ用） |
| プロファイル表示 | sys_post | SELECT | 役職情報取得（役職グループ用） |
| 個人情報更新 | sys_user | UPDATE | ユーザー情報更新 |
| パスワード変更 | sys_user | UPDATE | パスワード、ソルト更新 |
| アバター更新 | sys_user | UPDATE | アバターパス更新 |

### テーブル別操作詳細

#### sys_user

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | user_id, login_name, user_name, email, phonenumber, sex, avatar, password, salt | WHERE user_id = #{userId} | プロファイル表示 |
| UPDATE | user_name | フォーム入力値 | 個人情報更新 |
| UPDATE | email | フォーム入力値 | 個人情報更新 |
| UPDATE | phonenumber | フォーム入力値 | 個人情報更新 |
| UPDATE | sex | フォーム入力値 | 個人情報更新 |
| UPDATE | password, salt | 暗号化後の値 | パスワード変更 |
| UPDATE | avatar | アップロードファイルパス | アバター更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 認証エラー | 旧パスワードが一致しない | 「修改密码失败，旧密码错误」メッセージを表示 |
| - | バリデーションエラー | 新旧パスワードが同一 | 「新密码不能与旧密码相同」メッセージを表示 |
| - | 一意性違反 | 電話番号が重複 | 「修改用户'xxx'失败，手机号码已存在」メッセージを表示 |
| - | 一意性違反 | メールアドレスが重複 | 「修改用户'xxx'失败，邮箱账号已存在」メッセージを表示 |
| - | ファイルエラー | アバターアップロード失敗 | 例外メッセージを表示 |
| - | システムエラー | DB更新失敗 | 「修改密码异常，请联系管理员」メッセージを表示 |

### リトライ仕様

本機能にリトライ機能はない。エラー発生時はユーザーが再度操作を行う。

## トランザクション仕様

- 個人情報更新：単一UPDATE文のため暗黙的トランザクション
- パスワード変更：単一UPDATE文のため暗黙的トランザクション
- アバター更新：DB更新成功後にファイル削除を実行（ファイル操作はトランザクション外）

## パフォーマンス要件

- プロファイル表示: 500ms以内
- 個人情報更新: 1s以内
- パスワード変更: 1s以内
- アバターアップロード: 3s以内（ファイルサイズ依存）

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

- パスワード変更時は必ず旧パスワードの照合を行う
- パスワードはMD5 + Salt方式で暗号化して保存
- アバターファイルは画像形式のみ許可（MimeTypeUtils.IMAGE_EXTENSION）
- UUID命名によりファイル名からの情報漏洩を防止
- ログイン中のユーザー情報のみアクセス可能（セッション認証）
- 操作ログ（@Log）によるパスワード変更・個人情報変更の監査

## 備考

- アバター保存先: {profile}/avatar/ ディレクトリ
- アバターファイル命名: UUID形式
- パスワード暗号化: SysPasswordService.encryptPassword()を使用
- セッション更新: 変更後は即座にセッション内のユーザー情報を更新

---

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

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

### 推奨読解順序

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

まず、ユーザー情報のエンティティ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SysUser.java | `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java` | ユーザーエンティティのフィールド定義 |

**読解のコツ**:
- `userName`, `email`, `phonenumber`, `sex`, `avatar`などのプロファイル関連フィールドに注目
- `password`, `salt`のセキュリティ関連フィールドの暗号化方式を確認

#### Step 2: コントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SysProfileController.java | `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java` | 全エンドポイントの処理フロー |

**読解のコツ**:
- `getSysUser()`でセッションからログインユーザーを取得
- `setSysUser()`で更新後にセッション情報を同期

**主要処理フロー**:
- **50-58行目**: プロファイル表示処理。ユーザー情報、ロールグループ、役職グループを取得
- **60-66行目**: パスワード確認API。passwordService.matches()で照合
- **76-98行目**: パスワード変更処理。旧パスワード照合→新旧比較→暗号化→DB更新
- **125-149行目**: 個人情報更新処理。電話番号・メールの一意性チェック後に更新
- **154-184行目**: アバター更新処理。ファイルアップロード→DB更新→旧ファイル削除

#### Step 3: パスワードサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SysPasswordService.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java` | パスワード暗号化・照合ロジック |

**主要処理フロー**:
- **71-74行目**: matches()メソッド。暗号化後のパスワードと保存値を比較
- **81-84行目**: encryptPassword()メソッド。loginName + password + saltをMD5ハッシュ化

#### Step 4: ユーザーサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ISysUserService.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java` | サービスインターフェース定義 |
| 4-2 | SysUserServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java` | サービス実装 |

**主要処理フロー（SysUserServiceImpl）**:
- **273-277行目**: updateUserInfo() - 個人情報更新
- **286-289行目**: updateUserAvatar() - アバターパス更新
- **323-328行目**: resetUserPwd() - パスワードリセット
- **476-485行目**: selectUserRoleGroup() - ロールグループ取得
- **493-502行目**: selectUserPostGroup() - 役職グループ取得

#### Step 5: ファイルアップロードを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | FileUploadUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java` | ファイルアップロード処理 |
| 5-2 | FileUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java` | ファイル削除処理 |

**主要処理フロー（FileUploadUtils）**:
- **122-139行目**: upload()メソッド。ファイルサイズ・拡張子チェック、UUID命名、ファイル保存
- **152-155行目**: uuidFilename()メソッド。日付パス + UUID形式のファイル名生成

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

```
SysProfileController
    │
    ├─ profile()
    │      ├─ getSysUser()
    │      ├─ userService.selectUserRoleGroup()
    │      │      └─ roleMapper.selectRolesByUserId()
    │      └─ userService.selectUserPostGroup()
    │             └─ postMapper.selectPostsByUserId()
    │
    ├─ checkPassword()
    │      └─ passwordService.matches()
    │             └─ encryptPassword() + equals()
    │
    ├─ resetPwd()
    │      ├─ passwordService.matches() [旧パスワード照合]
    │      ├─ passwordService.matches() [新旧比較]
    │      ├─ ShiroUtils.randomSalt()
    │      ├─ passwordService.encryptPassword()
    │      ├─ userService.resetUserPwd()
    │      │      └─ userMapper.resetUserPwd()
    │      └─ setSysUser()
    │
    ├─ update()
    │      ├─ userService.checkPhoneUnique()
    │      ├─ userService.checkEmailUnique()
    │      ├─ userService.updateUserInfo()
    │      │      └─ userMapper.updateUser()
    │      └─ setSysUser()
    │
    └─ updateAvatar()
           ├─ FileUploadUtils.upload()
           │      ├─ assertAllowed()
           │      ├─ uuidFilename()
           │      └─ file.transferTo()
           ├─ userService.updateUserAvatar()
           │      └─ userMapper.updateUserAvatar()
           ├─ FileUtils.deleteFile() [旧アバター]
           └─ setSysUser()
```

### データフロー図

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

【プロファイル表示】
セッション ─────────▶ SysProfileController.profile() ────▶ ModelMap
                           │                                  ├─ user
                           ├─▶ selectUserRoleGroup()          ├─ roleGroup
                           └─▶ selectUserPostGroup()          └─ postGroup

【パスワード変更】
oldPassword ───────▶ passwordService.matches() ───▶ 照合結果
newPassword ───────▶ encryptPassword() ───────────▶ 暗号化パスワード
                           │
                           └─▶ userMapper.resetUserPwd() ───▶ sys_user更新

【アバター更新】
MultipartFile ─────▶ FileUploadUtils.upload() ────▶ ファイルパス
                           │                             │
                           └─▶ file.transferTo() ─────▶ {avatar}/yyyyMMdd/uuid.ext
                           │
                           └─▶ userMapper.updateUserAvatar() ───▶ sys_user更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SysProfileController.java | `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java` | コントローラー | プロファイル管理のエンドポイント |
| SysPasswordService.java | `ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/service/SysPasswordService.java` | サービス | パスワード暗号化・照合 |
| ISysUserService.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java` | インターフェース | ユーザーサービスインターフェース |
| SysUserServiceImpl.java | `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java` | サービス | ユーザーサービス実装 |
| FileUploadUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java` | ユーティリティ | ファイルアップロード処理 |
| FileUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java` | ユーティリティ | ファイル操作ユーティリティ |
| RuoYiConfig.java | `ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java` | 設定 | アバターパス等の設定 |
| MimeTypeUtils.java | `ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java` | ユーティリティ | 画像拡張子定義 |
