ありがとうございます。
ここから 第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
| 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:
| /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. 学習進捗管理
目的
レッスン単位で完了状態を管理します。
例:
| 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
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. レッスン完了処理
ボタン:
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. 動画視聴履歴
動画の場合、
「最後まで見たか」
だけではなく、
「何秒まで見たか」
を保持します。
例:
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:
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:
| <div
th:utext="${lesson.html}">
</div>
|
15. Quiz学習
構造:
表示:
| Javaの特徴は?
□ JVMで動作する
□ OS専用
[回答]
|
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
| @Mapper
public interface CourseProgressMapper {
CourseProgressDto findProgress(
Long userId,
Long courseId
);
}
|
21. 修了判定
条件例:
| 全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段階 では、教材管理システムを運用するための ファイル管理・動画配信基盤設計 に進みます。
内容:
-
Cloudflare R2連携
-
ファイルアップロード設計
-
動画HLS変換
-
ストリーミング配信
-
セキュアURL発行
-
ダウンロード制御
-
添付ファイル管理Service
-
Storage抽象化設計
を扱います。