この記事では、UniAppフレームワークを使用して開発された図書館自習室座席予約管理システムについて説明します。
UniAppとは
UniAppは、Vue.jsを基にしたクロスプラットフォーム開発フレームワークです。これにより、一度のコーディングでiOS、Android、Web、そして様々なミニプログラムプラットフォームへとデプロイが可能です。
- クロスプラットフォーム性: 一つのコードベースで複数のプラットフォームに対応可能。
- パフォーマンス最適化: ダイナミックコンパイルと静的コンパイルの組み合わせにより、ネイティブアプリのようなパフォーマンスを実現。
- 豊富なコンポーネントとAPI: 多様な機能を簡単に実装可能。
- コミュニティサポート: Vue.jsの広大なコミュニティから支援を得られる。
- 簡潔なアップデートと保守: コードの共有により、更新と保守が効率化される。
フロントエンドフレームワーク Vue
Vue.jsはJavaScriptフレームワークの一つで、多くの利点を持っています。特に、仮想DOM技術は効率的なDOM操作を可能にします。
Vue.jsは、リアクティブデータバインディング、仮想DOM、コンポーネント化などの現代的な技術を採用しており、開発者が柔軟で効率的にコードを記述できるように設計されています。
永続層フレームワーク MyBatis
MyBatisはオープンソースの永続層フレームワークで、データベース操作を簡素化します。SQLとJavaコードを分離し、XMLやアノテーションを使用してデータベース操作を記述します。
- データベース操作の簡素化: SQLマッピング機能により、Javaオブジェクトとデータベーステーブルを自動的にマッピング。
- 柔軟なSQL制御: 動的SQLにより、条件やロジックに基づいてSQLを動的に生成。
- キャッシュサポート: 1次キャッシュと2次キャッシュにより、データベースアクセスの頻度を減らし、パフォーマンス向上。
- 拡張性の高い構造: プラグインメカニズムにより、独自の機能を追加・カスタマイズ可能。
コードサンプル
// ログイン処理
@SkipAuthentication
@PostMapping("/login")
public ApiResponse login(String userName, String passWord, String captcha, HttpServletRequest request) {
User user = userService.findByUsername(userName);
if (user == null || !user.getPassword().equals(passWord)) {
return ApiResponse.error("ユーザー名またはパスワードが間違っています");
}
String authToken = tokenService.createToken(user.getId(), userName, "users", user.getRole());
return ApiResponse.success().put("token", authToken);
}
// トークン生成
@Override
public String createToken(Long userId, String userName, String tableName, String role) {
Token existingToken = this.findByUserIdAndRole(userId, role);
String newToken = RandomStringUtils.randomAlphanumeric(32);
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.HOUR_OF_DAY, 1);
if (existingToken != null) {
existingToken.setToken(newToken);
existingToken.setExpirationTime(calendar.getTime());
this.update(existingToken);
} else {
this.save(new Token(userId, userName, tableName, role, newToken, calendar.getTime()));
}
return newToken;
}
// トークン認証インターセプター
@Component
public class AuthInterceptor implements HandlerInterceptor {
public static final String TOKEN_HEADER = "Authorization";
@Autowired
private TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Token, Origin, Content-Type, cache-control,postman-token,Cookie, Accept,authorization");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
response.setStatus(HttpStatus.OK.value());
return false;
}
SkipAuthentication skipAuth;
if (handler instanceof HandlerMethod) {
skipAuth = ((HandlerMethod) handler).getMethodAnnotation(SkipAuthentication.class);
} else {
return true;
}
String token = request.getHeader(TOKEN_HEADER);
if (skipAuth != null) {
return true;
}
Token tokenEntity = null;
if (StringUtils.isNotBlank(token)) {
tokenEntity = tokenService.fetchByToken(token);
}
if (tokenEntity != null) {
request.getSession().setAttribute("userId", tokenEntity.getUserId());
request.getSession().setAttribute("role", tokenEntity.getRole());
request.getSession().setAttribute("tableName", tokenEntity.getTableName());
request.getSession().setAttribute("userName", tokenEntity.getUserName());
return true;
}
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
writer = response.getWriter();
writer.print(JSONObject.toJSONString(ApiResponse.error(401, "ログインしてください")));
} finally {
if (writer != null) {
writer.close();
}
}
return false;
}
}
データベーススキーマ
DROP TABLE IF EXISTS `addresses`;
CREATE TABLE `addresses` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_id` bigint(20) NOT NULL,
`address` varchar(255) NOT NULL,
`recipient_name` varchar(255) NOT NULL,
`phone_number` varchar(255) NOT NULL,
`is_default` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='住所情報';
DROP TABLE IF EXISTS `discussions`;
CREATE TABLE `discussions` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`title` varchar(255),
`content` text NOT NULL,
`parent_id` bigint(20),
`user_id` bigint(20) NOT NULL,
`user_name` varchar(255),
`avatar_url` text,
`status` varchar(255),
`is_pinned` tinyint(1) DEFAULT '0',
`pin_time` datetime,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='フォーラム';
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`account` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`full_name` varchar(255) NOT NULL,
`gender` varchar(255),
`phone` varchar(255),
`profile_picture` text,
`points` double DEFAULT '0',
`balance` double DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `account` (`account`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ユーザー情報';
テストケース
| 入力データ | 期待結果 | 実際結果 | 結果分析 |
|---|---|---|---|
| ユーザー名: admin, パスワード: 123456, 認証コード: 正しい | システムログイン | ログイン成功 | 結果一致 |
| ユーザー名: admin, パスワード: 111111, 認証コード: 正しい | パスワードエラー | パスワードが間違っています | 結果一致 |
| ユーザー名: admin, パスワード: 123456, 認証コード: 間違い | 認証コードエラー | 認証コードが間違っています | 結果一致 |
| ユーザー名: 空, パスワード: 123456, 認証コード: 正しい | ユーザー名必須 | ユーザー名を入力してください | 結果一致 |
| ユーザー名: admin, パスワード: 空, 認証コード: 正しい | パスワードエラー | パスワードが間違っています | 結果一致 |
ユーザー管理機能のテストケース
ユーザー追加
| 入力データ | 期待結果 | 実際結果 | 結果分析 |
|---|---|---|---|
| ユーザー名: user1, パスワード: 123456, 役割: 一般ユーザー | 追加成功、リストに表示 | user1がリストに表示 | 結果一致 |
| ユーザー名: user2, パスワード: 111111, 役割: 一般ユーザー | 追加成功、リストに表示 | user2がリストに表示 | 結果一致 |
| ユーザー名: user1, パスワード: 123456, 役割: 一般ユーザー | 追加失敗、既存ユーザー名エラー | 既存ユーザー名エラー | 結果一致 |
| ユーザー名: 空, パスワード: 123456, 役割: 一般ユーザー | 追加失敗、ユーザー名必須エラー | ユーザー名必須エラー | 結果一致 |
ユーザー編集
| 入力データ | 期待結果 | 実際結果 | 結果分析 |
|---|---|---|---|
| user1を選択し、パスワードを654321に変更 | 編集成功、パスワード変更 | user1のパスワードが654321に変更 | 結果一致 |
| user2を選択し、役割を管理者に変更 | 編集成功、役割変更 | user2の役割が管理者に変更 | 結果一致 |
| user1を選択し、ユーザー名をクリア | 編集失敗、ユーザー名必須エラー | ユーザー名必須エラー | 結果一致 |
ユーザー削除
| 入力データ | 期待結果 | 実際結果 | 結果分析 |
|---|---|---|---|
| user1を選択し削除 | 確認後削除 | user1が削除 | 結果一致 |
| user2を選択し削除をキャンセル | 削除しない | user2が削除されない | 結果一致 |