技術アーキテクチャ概要
本システムはモダンな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」) | 該当プロジェクトが非表示 | 一覧からプロジェクトが削除される | 合格 |
| プロジェクト作成時に必須項目を空欄 | バリデーションエラー表示 | 「プロジェクト名は必須です」が表示 | 合格 |
| 既存のプロジェクトコードを重複登録 | 重複エラーで登録失敗 | 「プロジェクトコードは既に使用されています」 | 合格 |
テスト評価
全ての機能テストケースを実行した結果、主要なビジネスフローについては要求仕様通りに動作することが確認できました。特に認証・認可機構において、セキュリティ要件を満たす適切なエラーハンドリングが実装されています。ただし、同時アクセス時のパフォーマンスについては別途負荷テストを実施する必要があります。