ありがとうございます。
ここから 第22段階:コース構成管理(Course・Section階層・Lesson・教材紐付け)実装設計 に進みます。
今回のポイントは、以前採用した 「教材ライブラリ方式」 を正式に反映することです。
つまり、
-
教材(Content)は独立管理
-
コースは教材を組み合わせる
-
同じ教材を複数コースで再利用可能
という構造にします。
第22段階 全体構成
1
2
3
4
5
6
7
8
9
10
11
12
13 | Course(コース)
|
+-- Section(章)
|
+-- Section(節)
| |
| +-- Lesson(レッスン)
|
+-- Lesson
|
+-- LessonContent
|
+-- Content(教材)
|
1. 設計方針
Course
役割:
「学習商品・講座単位」
例:
| Javaプログラミング入門
Spring Boot実践講座
新人エンジニア研修
|
Section
役割:
「教材の階層構造」
例:
| Java基礎
├ 第1章 Java概要
│
│ ├ Javaとは
│ └ JVMとは
│
├ 第2章 変数
│
│ ├ 型
│ └ 配列
|
Lesson
役割:
「受講者が学習する単位」
例:
| Lesson
Javaとは
↓
教材
動画
説明文
確認クイズ
|
2. Course 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 | @Entity
@Table(name="courses")
@Getter
@Setter
public class Course extends BaseEntity {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
private Long id;
private String title;
@Column(
columnDefinition="TEXT"
)
private String description;
@ManyToOne(fetch=FetchType.LAZY)
private User author;
@Enumerated(EnumType.STRING)
private CourseStatus status;
}
|
3. CourseStatus
| public enum CourseStatus {
DRAFT,
REVIEWING,
PUBLISHED,
CLOSED
}
|
4. Courseテーブル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | CREATE TABLE courses (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
description TEXT,
author_id BIGINT,
status VARCHAR(30),
created_at DATETIME,
updated_at DATETIME
);
|
5. Section 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 | @Entity
@Table(name="sections")
@Getter
@Setter
public class Section extends BaseEntity {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
private Long id;
@ManyToOne(fetch=FetchType.LAZY)
private Course course;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(
name="parent_id"
)
private Section parent;
private String name;
private Integer sortOrder;
}
|
6. Sectionテーブル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | CREATE TABLE sections (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
course_id BIGINT NOT NULL,
parent_id BIGINT,
name VARCHAR(200),
sort_order INT,
created_at DATETIME,
updated_at DATETIME
);
|
7. 階層例
DB:
| id |
course_id |
parent_id |
name |
| 1 |
1 |
null |
Java基礎 |
| 2 |
1 |
1 |
Java文法 |
| 3 |
1 |
2 |
変数 |
| 4 |
1 |
3 |
基本型 |
表示:
| Java基礎
└ Java文法
└ 変数
└ 基本型
|
8. Lesson Entity
LessonはSection配下です。
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 | @Entity
@Table(name="lessons")
@Getter
@Setter
public class Lesson extends BaseEntity {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
private Long id;
@ManyToOne(fetch=FetchType.LAZY)
private Section section;
private String title;
@Column(
columnDefinition="TEXT"
)
private String description;
private Integer sortOrder;
}
|
9. Lessonテーブル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | CREATE TABLE lessons (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
section_id BIGINT NOT NULL,
title VARCHAR(200),
description TEXT,
sort_order INT,
created_at DATETIME,
updated_at DATETIME
);
|
10. LessonContent設計
ここが重要です。
LessonとContentを直接1対1にしません。
理由:
1 Lessonに複数教材を登録できるため。
例:
| Lesson
Java変数とは
教材
①動画
②説明Markdown
③確認クイズ
|
11. LessonContent 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 | @Entity
@Table(name="lesson_contents")
@Getter
@Setter
public class LessonContent {
@Id
@GeneratedValue(
strategy=GenerationType.IDENTITY
)
private Long id;
@ManyToOne(fetch=FetchType.LAZY)
private Lesson lesson;
@ManyToOne(fetch=FetchType.LAZY)
private Content content;
private Integer sortOrder;
private Boolean required;
}
|
12. LessonContentテーブル
1
2
3
4
5
6
7
8
9
10
11
12
13 | CREATE TABLE lesson_contents (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
lesson_id BIGINT NOT NULL,
content_id BIGINT NOT NULL,
sort_order INT,
required BOOLEAN DEFAULT TRUE
);
|
13. 完成した教材再利用構造
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 | Content
Javaとは
(Spring Boot教材)
↑
LessonContent
↑
Lesson
Java概要
↑
Course
Java基礎講座
----------------
同じContent
↑
別Course
新人研修
|
14. コース作成フロー
教師操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | ① コース作成
↓
② セクション追加
↓
③ レッスン追加
↓
④ 教材ライブラリ検索
↓
⑤ 教材追加
↓
⑥ 公開申請
|
1
2
3
4
5
6
7
8
9
10
11
12 | @Data
public class CourseCreateForm {
@NotBlank
private String title;
private String description;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | @Data
public class SectionForm {
private Long parentId;
@NotBlank
private String name;
private Integer sortOrder;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | @Data
public class LessonForm {
private Long sectionId;
@NotBlank
private String title;
private String description;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | @Data
public class LessonContentForm {
private Long lessonId;
private Long contentId;
private Integer sortOrder;
private Boolean required;
}
|
19. Course編集画面
URL:
| /teacher/course/{id}/edit
|
画面イメージ:
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 | ------------------------------------------------
Java基礎講座
[章追加]
▼ 第1章 Java概要
▼ Java文法
Lesson:
Javaとは
[教材追加]
・動画 Javaとは
・Markdown 説明
・Quiz 確認問題
[保存]
------------------------------------------------
|
20. 階層表示Fragment
Thymeleaf:
| templates/components
_section-tree.html
|
呼び出し:
| <div
th:replace="
~{components/_section-tree
::
tree(section=${section})}">
</div>
|
21. ドラッグ&ドロップ並び替え
利用候補:
用途:
| Section順序変更
Lesson順序変更
教材順序変更
|
保存:
Ajax
↓
Controller
↓
sort_order更新
22. 並び替えController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | @PostMapping(
"/section/order"
)
@ResponseBody
public void updateOrder(
@RequestBody
List<OrderDto> orders){
sectionService
.updateOrder(
orders
);
}
|
23. Course公開処理
状態:
| DRAFT
↓
REVIEWING
↓
PUBLISHED
|
処理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | @Transactional
public void submit(
Long courseId){
Course course =
repository
.findById(courseId)
.get();
course.setStatus(
CourseStatus.REVIEWING
);
}
|
24. コース一覧検索
MyBatis担当。
検索条件:
DTO:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | @Data
public class CourseListDto {
private Long id;
private String title;
private String author;
private Integer lessonCount;
private String status;
}
|
25. CourseMapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | @Mapper
public interface CourseMapper {
List<CourseListDto> search(
CourseSearchCondition condition
);
CourseDetailDto findDetail(
Long id
);
}
|
26. 第22段階終了時点
実装可能:
✅ Course管理
✅ Section無限階層
✅ Lesson管理
✅ Lesson複数教材対応
✅ 教材ライブラリ再利用
✅ 教材順序管理
✅ セクション並び替え
✅ レッスン並び替え
✅ コース公開フロー
✅ 教師用コース編集基盤
次の 第23段階 では、受講者側の学習画面を実装します。
内容:
-
コース閲覧画面
-
受講開始処理
-
レッスン表示
-
動画プレイヤー
-
Markdown教材表示
-
クイズ回答処理
-
学習進捗更新
-
修了証発行設計
-
学習ダッシュボード
へ進みます。