【はじめてのJava】ダッシュボードAPIを作成しよう!③ログイン状況可視化編

はじめに

こんにちは!マーケターから未経験のエンジニアに転身した社員Tです。

開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。

今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。

これまでフロントエンド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。

本記事の対象の方

  • サーバーサイド開発をこれから学びたい方
  • JavaでのAPI開発に興味がある方
  • ダッシュボードの作成を通じて、実践的な技術を学びたい方

Javaとは?

Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。

詳しい説明については、過去のブログ記事をご参照ください。
⇒過去のブログ一覧

環境構築

環境構築に関しては、内容が広範囲にわたるため、別の記事で詳細に解説する予定です。

ダッシュボードAPIの作成

今回作成したいのは、こんなダッシュボード画面です。ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。

全体の作成の流れとしては、下記の通りとなります。

  1. ユーザー情報を取得
  2. コース情報を取得
  3. 各グラフ描画に必要なデータを取得

今回は、3.各グラフ描画に必要なデータを取得するAPIの実装について詳しく触れていきたいと思います。左上のログイン状況の表の制作に取り掛かりたいと思います。

1. ユーザー情報の取得については、過去のブログ記事をご参考ください。
⇒【はじめてのJava】ダッシュボードAPIを作成しよう!① ユーザー情報取得編

2. コース情報の取得については、過去のブログ記事をご参考ください。
⇒【はじめてのJava】ダッシュボードAPIを作成しよう!②コース情報取得編

ログイン情報取得APIの設計

取得したユーザーIDからログイン情報を取得し返却する。

エンドポイント

/top/getLoginHistoryByUserId

  • HTTPメソッド: POST
  • リクエストボディ: ユーザーID
  • レスポンス: ログイン履歴のマップ

リクエスト例

{
  "userId":"175"
}

今回はパスパラメータではなく、リクエストボディからユーザーIDを受け取る設計にしています。これにより、柔軟なデータの受け渡しが可能となります。

レスポンス例

{
  "dailyLoginHistoryMap": {
    "monday": false,
    "tuesday": false,
    "wednesday": false,
    "thursday": false,
    "friday": false,
    "saturday": false,
    "sunday": false
  },
  "errorMessageList": null
}

ログイン情報取得APIの実装

ログイン情報を管理するモデルクラスの作成

まず、ログイン履歴を保持するエンティティクラスを作成します。

LoginHistoryEntity.java
 1package jp.co.smsdatatech.questiongenerate.entity;
2 
3import java.io.Serializable;
4import java.sql.Timestamp;
5 
6import lombok.Data;
7 
8/**
9 * ログイン履歴テーブルEntity
10 */
11@Data
12public class LoginHistoryEntity implements Serializable {
13 
14 /** シリアルバージョンUID */
15 private static final long serialVersionUID = 1L;
16 
17 /** ユーザーID */
18 private Integer userId;
19 
20 /** 法人ID */
21 private Integer corporateId;
22 
23 /** ログイン日時 */
24 private Timestamp loginDate;
25}

MyBatisのMapper設定

次に、MyBatisのMapperを使用してデータベースからログイン履歴を取得します。getLoginHistoryByUserIdAndDateRange メソッドは、指定されたユーザーIDと日付範囲に基づいてログイン履歴を取得します。

LoginHistoryMapper.xml
 1<mapper namespace="jp.co.smsdatatech.questiongenerate.repository.LoginHistoryMapper">
2 
3 <select id="getLoginHistoryByUserIdAndDateRange" parameterType="map" resultType="java.time.LocalDateTime">
4 SELECT DISTINCT
5 login_date
6 FROM
7 login_history
8 WHERE
9 user_id = #{userId}
10 AND login_date BETWEEN #{startOfWeek} AND #{endOfWeek}
11 ORDER BY
12 login_date DESC
13 </select>
14</mapper>

サービスクラスの作成

サービスクラスで、ログイン履歴を取得するロジックを構築します。LoginHistoryService クラスでは、ユーザーIDを受け取り、ログイン履歴を取得します。

LoginHistoryService.java
 1public class LoginHistoryService {
2 
3 /**
4 * 指定されたユーザーIDに基づいてログイン履歴を取得する。
5 *
6 * @param userId ユーザーID
7 * @return ログイン履歴(曜日ごとの情報)
8 */
9 @Transactional(readOnly = true)
10 public Map<String, Boolean> getLoginHistoryByUserIdAndDateRange(Integer userId) {
11 // 週の開始日と終了日を計算
12 LocalDateTime startOfWeek = DateUtils.getStartOfWeek();
13 LocalDateTime endOfWeek = DateUtils.getEndOfWeek();
14 
15 log.debug("ユーザーID {} のログイン履歴を週の範囲 {} - {} で取得します。", userId, startOfWeek, endOfWeek);
16 
17 // データベースからログイン履歴を取得
18 List<LocalDateTime> loginHistory = loginHistoryMapper.getLoginHistoryByUserIdAndDateRange(userId, startOfWeek, endOfWeek);
19 
20 Map<String, Boolean> dailyLoginHistoryMap = DateUtils.initializeWeekMap();
21 
22 DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH);
23 for (LocalDateTime login : loginHistory) {
24 if (login != null) {
25 String dayOfWeek = login.format(dayFormatter).toLowerCase(Locale.ROOT);
26 dailyLoginHistoryMap.put(dayOfWeek, true);
27 }
28 }
29 return dailyLoginHistoryMap;
30 }
31}

コントローラークラスの作成

コントローラークラスでは、APIのエンドポイントを定義し、サービス層を呼び出してレスポンスを返します。

TopRest.java
 1public class TopRest {
2 
3 /**
4 * ログイン状況取得.
5 *
6 * login_historyテーブルからユーザーIDに紐づくログイン状況を取得し、LoginHistoryResponseFormに詰めて返却する。
7 *
8 * <h3>処理フロー</h3>
9 * 1. ログイン履歴を取得<br>
10 * 2. 取得データを返却
11 *
12 * <h3>レスポンス構成</h3>
13 * - **loginHistoryByDay**: 曜日ごとのログイン状況(キー: 曜日、値: true/false)<br>
14 * - **error**: エラー発生時のメッセージ
15 *
16 * @param form Top画面UserId用Form
17 * @param result バリデーション結果
18 * @return ログイン履歴情報 LoginHistoryResponseForm
19 */
20 @PostMapping("/top/getLoginHistoryByUserId")
21 public LoginHistoryResponseForm getLoginHistoryByUserId(@RequestBody @Valid UserIdForm form, BindingResult result) {
22 List<String> errorMessageList = new ArrayList<>();
23 LoginHistoryResponseForm responseForm = new LoginHistoryResponseForm();
24 
25 Set<String> processedFields = new HashSet<>();
26 
27 // バリデーションエラーがある場合、詳細なエラーメッセージを追加
28 if (result.hasErrors()) {
29 for (FieldError fieldError : result.getFieldErrors()) {
30 // フィールド名を基に重複チェック
31 if (!processedFields.contains(fieldError.getField())) {
32 String errorMessage = messageSource.getMessage(fieldError, Locale.getDefault());
33 errorMessageList.add(errorMessage);
34 processedFields.add(fieldError.getField());
35 }
36 }
37 responseForm.setErrorMessageList(errorMessageList);
38 return responseForm;
39 }
40 
41 // ユーザーIDを整数に変換
42 Integer userId = Integer.parseInt(form.getUserId());
43 
44 // サービス層で曜日ごとのログイン履歴を取得
45 Map<String, Boolean> dailyLoginHistoryMap = loginHistoryService.getLoginHistoryByUserIdAndDateRange(userId);
46 
47 // ログイン履歴が空かどうかをチェック
48 if (dailyLoginHistoryMap.isEmpty()) {
49 errorMessageList.add(messageSource.getMessage(ErrorMessages.NO_LOGIN_HISTORY, null, Locale.getDefault()));
50 responseForm.setErrorMessageList(errorMessageList);
51 return responseForm;
52 }
53 
54 responseForm.setDailyLoginHistoryMap(dailyLoginHistoryMap);
55 return responseForm;
56 }
57}

日付および曜日に関するユーティリティクラス

ログイン履歴の処理に役立つユーティリティメソッド(例:曜日の初期化や日付の範囲計算など)を別途 DateUtils クラスとして用意します。

DateUtils.java
 1public class DateUtils {
2 
3 /** 曜日をまとめた配列 */
4 protected static final String[] DAYS_OF_WEEK = {
5 "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"
6 };
7 
8 /**
9 * プライベートコンストラクタでインスタンス化を禁止する
10 */
11 private DateUtils() {
12 throw new UnsupportedOperationException("このクラスはインスタンス化できません");
13 }
14 
15 /**
16 * 指定された日付の週の開始日(月曜日の00:00:00)を取得.
17 *
18 * @param date 基準日
19 * @return 週の開始日
20 */
21 public static LocalDateTime getStartOfWeek(LocalDate date) {
22 return date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)).atStartOfDay();
23 }
24 
25 /**
26 * 現在週の開始日(月曜日の00:00:00)を取得.
27 *
28 * @return 現在週の開始日
29 */
30 public static LocalDateTime getStartOfWeek() {
31 return getStartOfWeek(LocalDate.now());
32 }
33 
34 /**
35 * 指定された日付の週の終了日(日曜日の23:59:59)を取得.
36 *
37 * @param date 基準日
38 * @return 週の終了日
39 */
40 public static LocalDateTime getEndOfWeek(LocalDate date) {
41 return date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)).atTime(LocalTime.MAX);
42 }
43 
44 /**
45 * 現在週の終了日(日曜日の23:59:59)を取得.
46 *
47 * @return 現在週の終了日
48 */
49 public static LocalDateTime getEndOfWeek() {
50 return getEndOfWeek(LocalDate.now());
51 }
52 
53 /**
54 * 曜日ごとの初期マップを作成(初期値はすべてfalse).
55 *
56 * @return 曜日をキーにしたマップ(値はすべてfalse)
57 */
58 public static Map<String, Boolean> initializeWeekMap() {
59 Map<String, Boolean> weekMap = new LinkedHashMap<>();
60 for (String day : DAYS_OF_WEEK) {
61 weekMap.put(day, false);
62 }
63 return weekMap;
64 }
65}

実装内容の振り返り

今回の実装で、ログイン履歴を週ごとに取得する処理において、複数回使用される処理(例えば、曜日の初期化や日付の範囲計算)をユーティリティクラスにまとめることができました。この部分は非常に勉強になりました。

ユーティリティクラスを作成することで、以下のようなメリットを実感できました。

  • 再利用性の向上:一度作成した共通部品を他の部分でも利用できるようになり、コードの重複を減らすことができました。これにより、将来的にメンテナンスがしやすくなります。
  • 可読性の向上:ユーティリティメソッドを使うことで、メインのビジネスロジックがシンプルになり、読みやすくなりました。例えば、曜日の初期化処理や日付範囲の計算などがユーティリティクラスに隠蔽されているため、コントローラークラスがすっきりとしたものになりました。
  • テストのしやすさ:共通の処理をユーティリティクラスにまとめることで、その部分を個別にテストすることができ、ロジックの正確性を担保しやすくなりました。

これらを踏まえて、ユーティリティクラスはシステム全体のコードをより効率的に、そして保守しやすくするための強力なツールだと改めて実感しました。

まとめ

効率的なコードを書くための方法を一つ引き出しを増やせたことが良かったです。特に、複数のクラスで使い回す共通の処理や汎用的で一貫性が求められる処理をユーティリティクラスにまとめることが、コードの効率化や保守性向上に大いに役立つと感じました。

次回も、各グラフの作成方法について記載しますのでお楽しみに!

筆者イメージ

筆者:K・T(20代)

所属:ハイブリット・マルチクラウド部

マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。
好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。
マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。

まずはお気軽にご相談ください
お問い合わせフォーム

その他のイベント・セミナー 一覧へ