SpringBootとVue.jsを活用した自習室予約管理システムの構築

システム概要

本システムは、自習室の座席予約、利用時間管理、ユーザ認証を統合的に管理するWebアプリケーションです。バックエンドにはSpringBootフレームワークを採用し、フロントエンドにはVue.jsを使用したモダンなアーキテクチャを実現しています。RESTful API設計により、拡張性とメンテナンス性を高めています。

バックエンドアーキテクチャ

Java17とSpringBoot 3.xをコア技術として採用しています。依存性注入(DI)とアスペクト指向プログラミング(AOP)を活用したレイヤードアーキテクチャにより、ビジネスロジックとデータアクセス層を明確に分離しています。セキュリティにはJWTトークンベースの認証方式を実装し、パスワードはBCryptアルゴリズムでハッシュ化して保存します。

認証API実装例

以下に、会員認証機能のコントローラ実装を示します。トークン生成、パスワード検証、ロールベースアクセス制御を含む完全な認証フローです。


/**
 * 会員管理エンドポイント
 */
@RestController
@RequestMapping("/api/members")
public class AccountManagerController {
    
    @Autowired
    private AccountService accountService;
    
    @Autowired
    private SessionService sessionService;

    /**
     * 認証処理
     */
    @PostMapping("/auth")
    public ResponseEntity<AuthResult> authenticate(@RequestBody CredentialDto loginInfo) {
        MemberAccount account = accountService.findByLoginId(loginInfo.getLoginId());
        
        if (account == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                               .body(new AuthResult("アカウントが存在しません"));
        }
        
        if (!account.getUserRole().equals(loginInfo.getRole())) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                               .body(new AuthResult("ロール権限が一致しません"));
        }
        
        if (!PasswordEncoder.matches(loginInfo.getPassword(), account.getCredential())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                               .body(new AuthResult("認証情報が不正です"));
        }
        
        String jwtToken = sessionService.createToken(account.getAccountId(), 
                                                    account.getLoginId(), 
                                                    account.getUserRole());
        return ResponseEntity.ok(new AuthResult(jwtToken, "認証成功"));
    }

    /**
     * アカウント新規登録
     */
    @PostMapping("/register")
    public ResponseEntity<String> createAccount(@RequestBody MemberAccount newAccount) {
        if (accountService.existsByLoginId(newAccount.getLoginId())) {
            return ResponseEntity.badRequest().body("ログインIDは既に使用されています");
        }
        newAccount.setCredential(PasswordEncoder.encode(newAccount.getCredential()));
        accountService.save(newAccount);
        return ResponseEntity.ok("アカウント作成完了");
    }

    /**
     * セッション終了
     */
    @PostMapping("/logout")
    public ResponseEntity<String> terminateSession(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        sessionService.invalidateToken(token);
        return ResponseEntity.ok("ログアウト処理完了");
    }

    /**
     * パスワード再発行
     */
    @PutMapping("/credentials/reset")
    public ResponseEntity<String> restoreCredentials(@RequestParam String loginId) {
        MemberAccount target = accountService.findByLoginId(loginId);
        if (target == null) {
            return ResponseEntity.notFound().build();
        }
        String temporaryPassword = PasswordGenerator.createTemporaryPass();
        target.setCredential(PasswordEncoder.encode(temporaryPassword));
        accountService.update(target);
        return ResponseEntity.ok("一時パスワード: " + temporaryPassword);
    }

    /**
     * ページネーション付き一覧取得
     */
    @GetMapping("/list")
    public ResponseEntity<PageResult<MemberAccount>> fetchList(
            @RequestParam Map<String, Object> conditions,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        
        PageRequest pageable = PageRequest.of(page - 1, size);
        Page<MemberAccount> result = accountService.searchWithConditions(conditions, pageable);
        return ResponseEntity.ok(PageResult.from(result));
    }

    /**
     * アカウント詳細取得
     */
    @GetMapping("/details/{accountId}")
    public ResponseEntity<MemberAccount> fetchDetails(@PathVariable Long accountId) {
        return ResponseEntity.ok(accountService.findById(accountId));
    }

    /**
     * 現在のセッション情報
     */
    @GetMapping("/current-session")
    public ResponseEntity<MemberAccount> fetchCurrentSession(HttpServletRequest request) {
        Long currentUserId = (Long) request.getAttribute("authenticatedUserId");
        return ResponseEntity.ok(accountService.findById(currentUserId));
    }

    /**
     * アカウント情報更新
     */
    @PutMapping("/modify")
    public ResponseEntity<String> modifyAccount(@RequestBody MemberAccount updates) {
        accountService.updateSelective(updates);
        return ResponseEntity.ok("更新処理完了");
    }

    /**
     * 一括削除
     */
    @DeleteMapping("/batch-remove")
    public ResponseEntity<String> removeAccounts(@RequestBody List<Long> targetIds) {
        accountService.deleteByIdList(targetIds);
        return ResponseEntity.ok("削除処理完了");
    }
}

データベース設計

データ永続化層にはMySQL 8.0を採用し、InnoDBストレージエンジンを使用しています。読み取り/書き込み分離とインデックス最適化を実施し、同時接続500件を想定したパフォーマンスチューニングを行っています。

会員アカウントテーブル定義


-- 会員管理テーブル
DROP TABLE IF EXISTS `member_accounts`;

CREATE TABLE `member_accounts` (
  `account_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'アカウント識別子',
  `login_id` VARCHAR(50) NOT NULL UNIQUE COMMENT 'ログイン用ID',
  `credential` VARCHAR(255) NOT NULL COMMENT 'ハッシュ化パスワード',
  `user_role` ENUM('STUDENT', 'ADMIN', 'GUEST') NOT NULL DEFAULT 'STUDENT' COMMENT 'ユーザロール',
  `email` VARCHAR(100) COMMENT '連絡先メール',
  `account_status` TINYINT NOT NULL DEFAULT 1 COMMENT 'ステータス(1:有効 0:無効)',
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
  `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
  PRIMARY KEY (`account_id`),
  KEY `idx_login_id` (`login_id`),
  KEY `idx_role_status` (`user_role`, `account_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会員アカウント管理テーブル';

-- 初期管理ユーザ
INSERT INTO `member_accounts` 
(`login_id`, `credential`, `user_role`, `email`, `account_status`) 
VALUES 
('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBaWVaN1oT7uWm', 'ADMIN', 'admin@studyroom.local', 1);

フロントエンド実装

Vue.js 3とTypeScript、Viteビルドツールを採用しています。Piniaによる状態管理とVue Routerによるルーティング管理を実装し、Element Plus UIフレームワークで一貫したユーザーインターフェースを提供します。Axiosを使用したAPIクライアントにはリクエスト/レスポンスインターセプターを設定し、トークンの自動付与とエラーハンドリングを集中管理しています。

主要機能モジュール

  • 座席管理: リアルタイム座席状況表示、予約・解放処理、利用時間監視
  • 予約スケジューラ: 時間帯別予約制限、重複予約防止、キャンセル待ち機能
  • 利用料金計算: 時間単位料金算出、割引ルール適用、請求書生成
  • アクセス制御: QRコード入退室管理、顔認証連携、不正利用検知
  • 統計分析: 利用頻度分析、収益レポート、混雑予測グラフ

本システムはマイクロサービスアーキテクチャへの拡張も見据え、Dockerコンテナ化とKubernetesオーケストレーション対応を前提に設計されています。APIゲートウェイ経由でのサービス連携と、Redisによるキャッシュ戦略の導入により、今後の大規模展開に柔軟に対応可能です。

タグ: SpringBoot vue.js Java MySQL 認証システム

7月30日 17:20 投稿