コンテンツにスキップ

ありがとうございます。

ここから 第13段階:監査ログ + 添付ファイル管理 + Cloudflare R2設計 に進みます。

今回の段階では、教材管理システムを本番運用するうえで重要な以下を設計します。

  • 誰が教材を作成・変更・申請・承認したか記録

  • 教材添付ファイルを共通管理

  • Cloudflare R2を利用した動画・ファイル管理

  • Spring AOPによる操作履歴自動記録


第13段階 全体構成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
操作

教師
 ↓
Controller
 ↓
Service
 ↓
AOP
 ↓
AuditLog


ファイル

Content
 ↓
Attachment
 ↓
StorageService
 ↓
Cloudflare R2

1. 監査ログ設計

目的

例えば以下を確認できるようにします。

1
2
3
4
5
6
7
2026/08/04 10:30

山田先生

教材「Java入門」

Version2を作成

また、

1
2
3
4
5
管理者

教材「Java入門」

Version2を公開

も記録します。


2. AuditLogテーブル

DDL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE audit_logs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,

    user_id BIGINT NOT NULL,

    action_type VARCHAR(50) NOT NULL,

    target_type VARCHAR(50) NOT NULL,

    target_id BIGINT,

    description TEXT,

    created_at DATETIME NOT NULL,

    CONSTRAINT fk_audit_user
    FOREIGN KEY(user_id)
    REFERENCES users(id)
);

3. action_type

Enum管理します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public enum AuditAction {

    CREATE,

    UPDATE,

    DELETE,

    SUBMIT,

    APPROVE,

    PUBLISH,

    LOGIN

}

4. Entity

AuditLog.java

 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
@Entity
@Table(name="audit_logs")
@Getter
@Setter
public class AuditLog {

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


    @ManyToOne(fetch=FetchType.LAZY)
    private User user;


    @Enumerated(EnumType.STRING)
    private AuditAction actionType;


    private String targetType;


    private Long targetId;


    @Column(columnDefinition="TEXT")
    private String description;


    private LocalDateTime createdAt;

}

5. Repository

1
2
3
4
5
@Repository
public interface AuditLogRepository
        extends JpaRepository<AuditLog,Long>{

}

6. AuditService

監査ログ登録専用サービスを作ります。

1
2
3
4
5
service

└ audit

   └ AuditService

AuditService.java

 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
@Service
@RequiredArgsConstructor
public class AuditService {

    private final AuditLogRepository repository;


    @Transactional
    public void save(
            User user,
            AuditAction action,
            String targetType,
            Long targetId,
            String description){


        AuditLog log =
            new AuditLog();


        log.setUser(user);

        log.setActionType(action);

        log.setTargetType(
            targetType
        );

        log.setTargetId(
            targetId
        );

        log.setDescription(
            description
        );

        log.setCreatedAt(
            LocalDateTime.now()
        );


        repository.save(log);

    }
}

7. Spring AOPによる自動記録

Serviceへ個別に、

1
auditService.save()

を書く方法もあります。

しかし、増えると管理が難しくなります。

そこでAOPを利用します。


8. Auditアノテーション作成

1
2
3
4
5
6
7
8
9
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {

    AuditAction action();

    String target();

}

利用例:

1
2
3
4
5
6
7
@Audit(
 action=AuditAction.PUBLISH,
 target="CONTENT"
)
public void publish(Long id){

}

9. AuditAspect

 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
@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {


    private final AuditService auditService;

    private final LoginUser loginUser;


    @AfterReturning("@annotation(audit)")
    public void after(
            JoinPoint joinPoint,
            Audit audit){


        User user =
            loginUser.get();


        Object[] args =
            joinPoint.getArgs();


        Long id =
            (Long)args[0];


        auditService.save(
            user,
            audit.action(),
            audit.target(),
            id,
            joinPoint
            .getSignature()
            .getName()
        );

    }
}

10. 利用例

教材公開:

1
2
3
4
5
6
7
8
9
@Audit(
 action=AuditAction.PUBLISH,
 target="CONTENT"
)
@Transactional
public void publish(
        Long versionId){

}

自動的に:

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

USER
山田

ACTION
PUBLISH

TARGET
CONTENT

ID
100

TIME
2026-08-04

が登録されます。


11. 添付ファイル共通化

以前提案した教材タイプ別管理:

1
2
3
VideoContent

FileContent

とは別に、

すべてのファイルを共通管理します。


12. Attachment設計

用途:

  • PDF

  • ZIP

  • Word

  • Excel

  • 動画

  • 画像

すべて対応します。


13. テーブル

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

    id BIGINT AUTO_INCREMENT PRIMARY KEY,

    original_name VARCHAR(255),

    stored_name VARCHAR(255),

    storage_type VARCHAR(20),

    storage_path VARCHAR(500),

    mime_type VARCHAR(100),

    file_size BIGINT,

    uploaded_by BIGINT,

    created_at DATETIME

);

14. Entity

Attachment.java

 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="attachments")
@Getter
@Setter
public class Attachment {


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


    private String originalName;


    private String storedName;


    private String storageType;


    private String storagePath;


    private String mimeType;


    private Long fileSize;


    @ManyToOne(fetch=FetchType.LAZY)
    private User uploadedBy;


    private LocalDateTime createdAt;

}

15. Contentとの関連

多対多にします。

理由:

同じPDFを複数教材で利用可能にするためです。


中間テーブル

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

    content_id BIGINT,

    attachment_id BIGINT,

    PRIMARY KEY(
        content_id,
        attachment_id
    )

);

構造:

1
2
3
4
5
6
7
8
9
Content

 +

Attachment

↓

ContentAttachment

16. StorageService設計

保存先を抽象化します。

1
2
3
4
5
6
7
service

└ storage

   ├ StorageService
   ├ LocalStorageService
   └ R2StorageService

StorageService

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public interface StorageService {


    String upload(
        MultipartFile file
    );


    Resource download(
        String path
    );


    void delete(
        String path
    );

}

17. Cloudflare R2構成

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

↓

Spring Boot

↓

StorageService

↓

Cloudflare R2

↓

Object Storage

18. R2保存パス設計

推奨:

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

 ├ videos/

 │   └ content001/

 │       └ master.m3u8


 ├ documents/

 │   └ content001/

 │       └ sample.pdf


 └ images/

     └ content001/

19. 動画設計

MP4直配信ではなく将来的にはHLS推奨です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
動画アップロード

↓

FFmpeg変換

↓

HLS生成

↓

R2保存


master.m3u8

segment001.ts

segment002.ts

20. VideoContent変更案

現在:

1
video_url

変更推奨:

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

youtube
vimeo
r2


external_id

youtube動画ID


storage_key

videos/content001/master.m3u8

Entity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Entity
public class VideoContent {


    private String videoType;


    private String externalId;


    private String storageKey;


}

21. ファイルアップロード処理

Controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@PostMapping("/upload")
public String upload(
        MultipartFile file){

    String path =
        storageService.upload(file);


    return path;

}

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
@Transactional
public Long saveFile(
        MultipartFile file){

    String path =
        storageService.upload(file);


    Attachment attachment =
        new Attachment();


    attachment.setStoragePath(
        path
    );


    repository.save(
        attachment
    );


    return attachment.getId();

}

22. セキュリティ制御

重要です。

ファイルURLを直接公開しません。

NG:

1
https://r2.xxx.com/sample.pdf

OK:

1
/download/attachment/100

Controller:

1
2
3
4
5
6
7
8
9
@GetMapping(
"/download/{id}"
)
public ResponseEntity<Resource> download(
        @PathVariable Long id){

    return storageService.download(id);

}

23. 現在の完成構造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
                 Content

                    |
        +-----------+-----------+

        Version             Attachment

          |                    |

 Video/Text/Quiz/File      R2 Storage




操作

 User

  |

AuditLog

第13段階終了時点

追加された機能:

✅ 操作履歴管理
✅ Spring AOP監査ログ
✅ 添付ファイル共通化
✅ R2対応可能なStorage設計
✅ 動画ストリーミング拡張設計
✅ ファイルアクセス制御


次の 第14段階 では、受講機能(LMS部分)を設計します。

内容:

  1. 受講登録

  2. コース受講状態管理

  3. レッスン進捗管理

  4. 動画視聴履歴

  5. クイズ回答履歴

  6. 修了判定

  7. 学習ダッシュボード

  8. メンター向け進捗確認(将来拡張)

へ進みます。