コンテンツにスキップ

ありがとうございます。

ここから 第23段階:受講者学習機能(LMS学習画面・進捗管理・修了管理)実装設計 に進みます。

今回は、受講者が実際に利用するLMS部分を実装レベルまで設計します。

対象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
受講者

 ↓

コース一覧

 ↓

受講開始

 ↓

レッスン学習

 ↓

教材閲覧

 ↓

進捗保存

 ↓

修了判定

 ↓

修了証発行

第23段階 全体構成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
                Course

                  |

             Enrollment

                  |

                Lesson

                  |

          +-------+-------+

       Video    Text    Quiz

                  |

          LearningProgress

                  |

             Completion

                  |

            Certificate

1. 受講登録(Enrollment)

役割

「誰が、どのコースを学習しているか」

を管理します。


Enrollment Entity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Entity
@Table(name="enrollments")
@Getter
@Setter
public class Enrollment extends BaseEntity {


    @Id
    @GeneratedValue(
        strategy=GenerationType.IDENTITY
    )
    private Long id;


    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(
        name="user_id"
    )
    private User user;


    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(
        name="course_id"
    )
    private Course course;


    @Enumerated(EnumType.STRING)
    private EnrollmentStatus status;


    private LocalDateTime startedAt;


    private LocalDateTime completedAt;

}

2. EnrollmentStatus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public enum EnrollmentStatus {


    ACTIVE,

    COMPLETED,

    CANCELLED

}

3. Enrollmentテーブル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE enrollments (

id BIGINT AUTO_INCREMENT PRIMARY KEY,

user_id BIGINT NOT NULL,

course_id BIGINT NOT NULL,

status VARCHAR(30),

started_at DATETIME,

completed_at DATETIME,

created_at DATETIME,

updated_at DATETIME,

UNIQUE(
user_id,
course_id
)

);

4. 受講開始処理

URL:

1
/student/course/{id}/start

処理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
コース確認

↓

公開中か確認

↓

Enrollment作成

↓

学習画面へ移動

Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Service
@RequiredArgsConstructor
public class EnrollmentService {


private final EnrollmentRepository repository;



@Transactional
public void start(
User user,
Course course){


Enrollment enrollment =
new Enrollment();


enrollment.setUser(
user
);


enrollment.setCourse(
course
);


enrollment.setStatus(
EnrollmentStatus.ACTIVE
);


enrollment.setStartedAt(
LocalDateTime.now()
);


repository.save(
enrollment
);


}

}

5. 学習進捗管理

目的

レッスン単位で完了状態を管理します。

例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Java基礎講座

第1章

✓ Javaとは

✓ JVMとは

△ 変数

進捗 66%

6. LearningProgress Entity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Entity
@Table(name="learning_progress")
@Getter
@Setter
public class LearningProgress extends BaseEntity {


@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
private Long id;


@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(
name="user_id"
)
private User user;


@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(
name="lesson_id"
)
private Lesson lesson;


@Enumerated(EnumType.STRING)
private ProgressStatus status;


private LocalDateTime startedAt;


private LocalDateTime completedAt;


}

7. ProgressStatus

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public enum ProgressStatus {


NOT_STARTED,


LEARNING,


COMPLETED


}

8. LearningProgressテーブル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE learning_progress (

id BIGINT AUTO_INCREMENT PRIMARY KEY,

user_id BIGINT,

lesson_id BIGINT,

status VARCHAR(30),

started_at DATETIME,

completed_at DATETIME,

created_at DATETIME,

updated_at DATETIME,

UNIQUE(
user_id,
lesson_id
)

);

9. レッスン開始処理

受講者が開いた時:

1
2
3
4
5
未開始

↓

学習中

へ変更します。


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Transactional
public void startLesson(
User user,
Lesson lesson){


LearningProgress progress =
repository
.findByUserIdAndLessonId(
user.getId(),
lesson.getId()
)
.orElse(
new LearningProgress()
);


progress.setUser(
user
);


progress.setLesson(
lesson
);


progress.setStatus(
ProgressStatus.LEARNING
);


progress.setStartedAt(
LocalDateTime.now()
);


repository.save(
progress
);


}

10. レッスン完了処理

ボタン:

1
[学習完了]

Controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@PostMapping(
"/lesson/{id}/complete"
)
public String complete(
@PathVariable Long id){


learningService
.completeLesson(
id
);


return
"redirect:/student/lesson/"+id;

}

Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Transactional
public void completeLesson(
Long lessonId){


LearningProgress progress =
repository
.findByLessonId(
lessonId
);


progress.setStatus(
ProgressStatus.COMPLETED
);


progress.setCompletedAt(
LocalDateTime.now()
);


}

11. 動画視聴履歴

動画の場合、

「最後まで見たか」

だけではなく、

「何秒まで見たか」

を保持します。


例:

1
2
3
4
5
動画 60分

視聴位置

35:20

12. VideoWatchHistory Entity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Entity
@Table(name="video_watch_history")
@Getter
@Setter
public class VideoWatchHistory {


@Id
@GeneratedValue
private Long id;


@ManyToOne
private User user;


@ManyToOne
private Content content;


private Integer watchedSeconds;


private Integer lastPosition;


private LocalDateTime updatedAt;


}

13. 動画保存API

Ajax:

1
2
3
4
5
6
7
8
9
5秒ごと

↓

現在位置送信

↓

DB更新

Controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@PostMapping(
"/video/progress"
)
@ResponseBody
public void saveVideoProgress(
@RequestBody
VideoProgressForm form){


service.save(
form
);


}

14. Text教材表示

流れ:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
DB

Markdown

↓

MarkdownService

↓

HTML

↓

Thymeleaf表示

Controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@GetMapping(
"/lesson/{id}"
)
public String lesson(
@PathVariable Long id,
Model model){


LessonDetailDto lesson =
service.findLesson(id);


model.addAttribute(
"lesson",
lesson
);


return
"student/lesson/detail";

}

HTML:

1
2
3
<div
th:utext="${lesson.html}">
</div>

15. Quiz学習

構造:

1
2
3
4
5
6
7
8
9
Quiz

 |

Choice

 |

Answer

表示:

1
2
3
4
5
6
7
8
9
Javaの特徴は?


□ JVMで動作する

□ OS専用


[回答]

16. QuizAnswerForm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Data
public class QuizAnswerForm {


private Long quizId;


private List<Long>
choiceIds;


}

17. 回答判定Service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Transactional
public boolean answer(
QuizAnswerForm form){


List<Long> correctIds =
choiceRepository
.findCorrectIds(
form.getQuizId()
);


return
correctIds
.equals(
form.getChoiceIds()
);


}

18. QuizAnswerHistory

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Entity
@Table(
name="quiz_answer_histories"
)
@Getter
@Setter
public class QuizAnswerHistory {


@Id
@GeneratedValue
private Long id;


@ManyToOne
private User user;


@ManyToOne
private QuizContent quiz;


private Boolean correct;


private Integer score;


private LocalDateTime answeredAt;


}

19. コース進捗計算

MyBatis担当です。


DTO:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Data
public class CourseProgressDto {


private Long courseId;


private String courseName;


private Integer totalLessons;


private Integer completedLessons;



public Integer getRate(){


if(totalLessons==0){

return 0;

}


return
completedLessons
*100
/
totalLessons;


}

}

20. CourseProgressMapper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Mapper
public interface CourseProgressMapper {


CourseProgressDto findProgress(
Long userId,
Long courseId
);


}

21. 修了判定

条件例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
全Lesson完了

+

必須Quiz 80%以上


↓

修了

Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
@RequiredArgsConstructor
public class CompletionService {


public boolean check(
Long userId,
Long courseId){


CourseProgressDto progress =
mapper.findProgress(
userId,
courseId
);


return
progress.getRate()==100;


}

}

22. 修了証管理

Certificate Entity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity
@Table(name="certificates")
@Getter
@Setter
public class Certificate {


@Id
@GeneratedValue
private Long id;


@ManyToOne
private User user;


@ManyToOne
private Course course;


private String certificateNo;


private LocalDateTime issuedAt;


}

23. Certificateテーブル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CREATE TABLE certificates (

id BIGINT AUTO_INCREMENT PRIMARY KEY,

user_id BIGINT,

course_id BIGINT,

certificate_no VARCHAR(100),

issued_at DATETIME

);

24. 受講者画面構成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/student

├ dashboard

├ courses

│
└ {id}

    ├ overview

    └ lesson


├ history


└ certificates

25. 学習画面イメージ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
------------------------------------------------

Java基礎講座


第1章 Java概要


Lesson

Javaとは


-------------------------

動画

▶ 再生


-------------------------

説明

Javaとは...


-------------------------

確認問題

3問


-------------------------


[学習完了]


次へ >

------------------------------------------------

26. 第23段階終了時点

実装可能:

✅ 受講登録
✅ レッスン開始管理
✅ レッスン完了管理
✅ 動画視聴位置保存
✅ Markdown教材表示
✅ クイズ回答判定
✅ 回答履歴保存
✅ コース進捗計算
✅ 修了判定
✅ 修了証管理


次の 第24段階 では、教材管理システムを運用するための ファイル管理・動画配信基盤設計 に進みます。

内容:

  1. Cloudflare R2連携

  2. ファイルアップロード設計

  3. 動画HLS変換

  4. ストリーミング配信

  5. セキュアURL発行

  6. ダウンロード制御

  7. 添付ファイル管理Service

  8. Storage抽象化設計

を扱います。