技術スタック
バックエンドフレームワーク: SpringBoot
Spring BootはTomcatやJettyなどのサーバーを内蔵しており、追加の設定なしで利用可能です。自動設定機能により、プロジェクトの依存関係に基づいてアプリケーション構成を簡素化します。Spring DataやSpring Securityなどのモジュールが統合されており、開発効率が向上します。
フロントエンドフレームワーク: Vue.js
Vue.jsは仮想DOM技術を中核とし、データ変更時にUIを自動更新します。コンポーネントベースのアーキテクチャにより、柔軟で保守性の高い開発を実現します。
永続層フレームワーク: MyBatisPlus
MyBatis-PlusはMyBatisを拡張したORMツールで、SQL自動生成やCRUD操作を簡素化します。ページネーションや動的クエリ、楽観的ロックなどの機能を提供します。
システムテスト
テスト目的
システムの品質保証と信頼性向上を目的とし、ユーザー視点での機能検証を実施します。要件仕様との整合性を確認し、ユーザビリティの問題を早期に発見します。
機能テスト
ログイン機能テストケース
| 入力データ | 期待結果 | 実測結果 |
|---|---|---|
| 正しいユーザー/パスワード | ログイン成功 | システムにアクセス可能 |
| 誤ったパスワード | 認証エラー | パスワード再入力要求 |
| 空のユーザー名 | バリデーションエラー | ユーザー名入力要求 |
ユーザー管理テストケース
| 操作 | 期待結果 | 実測結果 |
|---|---|---|
| 新規ユーザー追加 | リストに表示 | 正常に登録 |
| 重複ユーザー登録 | 登録拒否 | 重複エラー表示 |
| ユーザー削除 | リストから消失 | 正常に削除 |
テスト結論
ブラックボックステストにより全機能を検証し、要件仕様との整合性を確認。ユーザビリティと機能性の両面で要求を満たすことを検証しました。
実装例
認証処理
@PostMapping("/login")
public ResponseEntity<?> authenticate(@RequestParam String userId,
@RequestParam String passphrase) {
UserAccount account = accountRepository.findByUserId(userId);
if (account == null || !account.verifyPassphrase(passphrase)) {
return ResponseEntity.status(UNAUTHORIZED).build();
}
String accessToken = tokenProvider.generateToken(account.getId());
return ResponseEntity.ok().body(Map.of("accessToken", accessToken));
}
トークン生成
public String createToken(Long accountId, String role) {
Token existingToken = tokenRepository.findByAccountId(accountId);
String newTokenValue = generateRandomString(32);
Date expiration = Date.from(Instant.now().plus(1, HOURS));
if (existingToken != null) {
existingToken.updateToken(newTokenValue, expiration);
} else {
tokenRepository.save(new Token(accountId, role, newTokenValue, expiration));
}
return newTokenValue;
}
認証インターセプター
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
if (method.hasMethodAnnotation(SkipAuth.class)) {
return true;
}
}
String tokenHeader = request.getHeader("Authorization");
if (StringUtils.isEmpty(tokenHeader)) {
response.setStatus(SC_UNAUTHORIZED);
return false;
}
Token token = tokenService.validateToken(tokenHeader);
if (token != null) {
request.setAttribute("currentUser", token.getAccountId());
return true;
}
response.setStatus(SC_UNAUTHORIZED);
return false;
}
}
データベース設計
トークンテーブル
CREATE TABLE access_tokens (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
account_id BIGINT NOT NULL,
token_value VARCHAR(200) NOT NULL UNIQUE,
role VARCHAR(50) NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);