Spring Boot、Vue.js、uni-appを活用したプロジェクト管理システムの設計と実装

技術アーキテクチャ概要

本システムはモダンなWeb技術を統合したプロジェクト管理プラットフォームです。バックエンドにはSpring Bootを採用し、フロントエンドWeb画面にVue.js、モバイルアプリにはuni-appを適用することで、マルチデバイス対応を実現しています。

バックエンド:Spring Boot

Spring Bootは独立実行可能なアプリケーション構築を可能にするフレームワークです。組み込みTomcatサーバーによりWARファイルの外部デプロイが不要で、JARファイルのまま実行可能です。自動設定機能により依存関係に基づいたスマートな設定が行われ、開発者が手動でBean定義を記述する手間を大幅に削減します。また、Spring Security、Spring Data JPAなどのエコシステムとの統合が容易で、認証・認可やデータアクセス層の実装を効率化できます。

フロントエンド:Vue.js

Vue.jsはプログレッシブなJavaScriptフレームワークで、仮想DOMとリアクティブデータバインディングが特徴です。データモデルとUIの自動同期により、手動でのDOM操作が不要になります。単一ファイルコンポーネント(SFC)によりテンプレート、ロジック、スタイルを1ファイルで管理でき、開発の生産性と保守性を向上させます。

モバイルフレームワーク:uni-app

uni-appはVue.jsベースのクロスプラットフォーム開発フレームワークです。1つのコードベースでiOS、Android、各種ミニプログラムに対応可能です。条件付きコンパイルによりプラットフォーム固有の最適化が行え、ネイティブライクなパフォーマンスを発揮します。

データアクセス層:MyBatis-Plus

MyBatis-PlusはMyBatisを拡張したORMフレームワークです。Lambda式を利用したタイプセーフなクエリ構築や、自動コード生成機能によりボイラープレートコードを削減できます。ページネーション、楽観的ロック、パフォーマンス分析などの実用的な機能が組み込まれており、複雑なSQLを効率的に処理できます。

認証・認可機構の実装

セッションベースの認証フロー

システムはステートフルなセッション管理を採用しています。ログイン成功時にサーバー側でセッショントークンを生成し、クライアントはHTTPヘッダーに付与してリクエストごとに送信します。

// 認証エンドポイント
@RestController
@RequestMapping("/api/auth")
public class AuthenticationController {
    
    @Autowired
    private AccountRepository accountRepo;
    
    @Autowired
    private SessionManager sessionMgr;
    
    @PostMapping("/login")
    public ResponseEntity<AuthResult> handleLogin(@RequestBody Credentials creds) {
        Account account = accountRepo.findByEmail(creds.getEmail());
        
        if (account == null || !PasswordUtil.matches(creds.getPassword(), account.getPasswordHash())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(AuthResult.failure("認証情報が一致しません"));
        }
        
        if (account.isLocked()) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body(AuthResult.failure("アカウントがロックされています"));
        }
        
        String accessToken = sessionMgr.createSession(
            account.getId(), 
            account.getEmail(), 
            account.getAccountType()
        );
        
        return ResponseEntity.ok(AuthResult.success(accessToken, account.getRole()));
    }
}

// トークン生成ロジック
@Component
public class SessionManager {
    
    @Autowired
    private SessionTokenMapper tokenMapper;
    
    public String createSession(Long userId, String identifier, String userType) {
        String token = UUID.randomUUID().toString().replace("-", "");
        LocalDateTime expiry = LocalDateTime.now().plusHours(8);
        
        SessionToken session = new SessionToken();
        session.setUserId(userId);
        session.setIdentifier(identifier);
        session.setUserCategory(userType);
        session.setAccessToken(token);
        session.setExpiresAt(expiry);
        
        tokenMapper.insert(session);
        
        return token;
    }
    
    public SessionToken validateToken(String token) {
        SessionToken session = tokenMapper.selectByToken(token);
        if (session == null || session.getExpiresAt().isBefore(LocalDateTime.now())) {
            return null;
        }
        return session;
    }
}

認可インターセプター

リクエストの事前処理でトークンを検証し、有効な場合にのみリクエストを処理します。

@Component
public class AccessControlInterceptor implements HandlerInterceptor {
    
    private static final String AUTH_HEADER = "X-Access-Token";
    
    @Autowired
    private SessionManager sessionMgr;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler) throws Exception {
        
        // CORSプリフライトリクエストの処理
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpStatus.OK.value());
            return false;
        }
        
        // 公開APIのチェック
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            if (method.hasMethodAnnotation(PublicApi.class)) {
                return true;
            }
        }
        
        String token = request.getHeader(AUTH_HEADER);
        if (token == null || token.trim().isEmpty()) {
            writeErrorResponse(response, "認証トークンが不足しています");
            return false;
        }
        
        SessionToken session = sessionMgr.validateToken(token);
        if (session == null) {
            writeErrorResponse(response, "無効なトークンです");
            return false;
        }
        
        // リクエスト属性にユーザー情報を設定
        request.setAttribute("currentUserId", session.getUserId());
        request.setAttribute("userCategory", session.getUserCategory());
        
        return true;
    }
    
    private void writeErrorResponse(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(
            "{\"status\":\"error\",\"message\":\"" + message + "\"}"
        );
    }
}

データベース設計

セッション管理テーブル

CREATE TABLE user_session (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT 'ユーザー識別子',
    identifier VARCHAR(100) NOT NULL COMMENT 'ログインID',
    user_category VARCHAR(50) COMMENT 'ユーザー種別',
    access_token VARCHAR(255) NOT NULL UNIQUE COMMENT 'アクセストークン',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NOT NULL COMMENT '有効期限',
    INDEX idx_token (access_token),
    INDEX idx_user (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ユーザーセッション管理テーブル';

-- サンプルデータ
INSERT INTO user_session VALUES 
(1, 1001, 'admin@example.com', '管理者', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', NOW(), '2024-12-31 23:59:59'),
(2, 2001, 'dev@example.com', '開発者', 'z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k', NOW(), '2024-12-31 23:59:59');

システムテスト戦略

テスト実施方針

本システムの品質保証は、機能テストと非機能テストの両面から実施します。特にエンドユーザーの操作シナリオを想定したブラックボックステストを中心に、要求仕様との整合性を検証します。

認証機能のテストケース

テスト条件期待結果実測結果検証結果
アカウント:admin001、パスワード:Pass1234、トークン:正しい値 ダッシュボード画面に遷移 ダッシュボードが正常表示 合格
アカウント:admin001、パスワード:WrongPass、トークン:正しい値 パスワード誤りのエラーメッセージ 「認証情報が一致しません」が表示 合格
アカウント:admin001、パスワード:Pass1234、トークン:空値 トークン未入力のエラー 「認証トークンが不足しています」が表示 合格
アカウント:未入力、パスワード:Pass1234、トークン:正しい値 アカウント未入力のエラー 「アカウントを入力してください」が表示 合格
存在しないアカウント:ghost@example.com 認証失敗のエラー 「認証情報が一致しません」が表示 合格

プロジェクト管理機能のテスト

操作内容期待動作実測動作評価
新規プロジェクト作成(全項目正しく入力) プロジェクト一覧に追加表示 一覧に新規プロジェクトが反映 合格
プロジェクト編集(名称を変更) 変更内容が即座に反映 一覧・詳細画面で変更が確認できる 合格
プロジェクト削除(削除確認ダイアログで「OK」) 該当プロジェクトが非表示 一覧からプロジェクトが削除される 合格
プロジェクト作成時に必須項目を空欄 バリデーションエラー表示 「プロジェクト名は必須です」が表示 合格
既存のプロジェクトコードを重複登録 重複エラーで登録失敗 「プロジェクトコードは既に使用されています」 合格

テスト評価

全ての機能テストケースを実行した結果、主要なビジネスフローについては要求仕様通りに動作することが確認できました。特に認証・認可機構において、セキュリティ要件を満たす適切なエラーハンドリングが実装されています。ただし、同時アクセス時のパフォーマンスについては別途負荷テストを実施する必要があります。

タグ: Spring Boot vue.js uni-app MyBatis-Plus トークン認証

6月9日 21:11 投稿