コンテンツにスキップ

ありがとうございます。

ここから 第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

役割:

「学習商品・講座単位」

例:

1
2
3
Javaプログラミング入門
Spring Boot実践講座
新人エンジニア研修

Section

役割:

「教材の階層構造」

例:

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

├ 第1章 Java概要
│
│  ├ Javaとは
│  └ JVMとは
│
├ 第2章 変数
│
│  ├ 型
│  └ 配列

Lesson

役割:

「受講者が学習する単位」

例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 基本型

表示:

1
2
3
4
5
6
7
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に複数教材を登録できるため。

例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
① コース作成

↓

② セクション追加

↓

③ レッスン追加

↓

④ 教材ライブラリ検索

↓

⑤ 教材追加

↓

⑥ 公開申請

15. コース作成Form

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


@NotBlank
private String title;


private String description;


}

16. Section登録Form

 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;


}

17. Lesson登録Form

 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;


}

18. 教材追加Form

 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:

1
/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:

1
2
3
templates/components

_section-tree.html

呼び出し:

1
2
3
4
5
6
<div
th:replace="
~{components/_section-tree
::
tree(section=${section})}">
</div>

21. ドラッグ&ドロップ並び替え

利用候補:

  • SortableJS

用途:

1
2
3
4
5
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公開処理

状態:

1
2
3
4
5
6
7
8
9
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担当。

検索条件:

1
2
3
4
5
6
7
8
9
タイトル

カテゴリ

作成者

公開状態

タグ

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段階 では、受講者側の学習画面を実装します。

内容:

  1. コース閲覧画面

  2. 受講開始処理

  3. レッスン表示

  4. 動画プレイヤー

  5. Markdown教材表示

  6. クイズ回答処理

  7. 学習進捗更新

  8. 修了証発行設計

  9. 学習ダッシュボード

へ進みます。