Tài liệu Thiết kế Cơ sở Dữ liệu
Module Quản lý Tệp tin
FILE
1. Tổng quan
1.1 Mục đích
Tài liệu này định nghĩa thiết kế cơ sở dữ liệu cho module Quản lý Tệp tin (cơ sở dữ liệu: file_db).
1.2 Yêu cầu được Giải quyết
| Yêu cầu SRS | Giải pháp Cơ sở dữ liệu |
| FR-001 Tải lên Tệp tin | Bảng sys_files (lưu trữ metadata) |
| FR-002 Tải xuống Tệp tin | Bảng sys_files (tra cứu file_path) |
| FR-003 Liệt kê Tệp tin | Bảng sys_files (truy vấn với chỉ mục) |
| FR-004 Xóa Tệp tin | Bảng sys_files (xóa mềm qua status/deleted_at) |
| FR-008 Tạo Thư mục | Bảng sys_files (type=FOLDER, parent_folder_id) |
| FR-014 Quản lý Quyền | Bảng sys_file_permissions |
| FR-016 Theo dõi Sử dụng Lưu trữ | Bảng sys_storage_usage |
| FR-017 Quản lý Hạn ngạch | Bảng sys_storage_quotas |
| FR-018 Chính sách Lưu trữ | Bảng sys_storage_policies |
1.3 Sơ đồ ERD
Hình 1: ERD Quản lý Tệp tin
2. Thiết kế Schema
2.1 Chiến lược Cơ sở dữ liệu
| Khía cạnh | Quyết định |
| Cơ sở dữ liệu | file_db |
| Đặt tên bảng | sys_<table_name> |
| Khóa chính | UUID (qua gen_random_uuid()) |
| Thời gian | created_at, updated_at với mặc định NOW() |
| Xóa mềm | Cột deleted_at (NULL = đang hoạt động) |
| Backend Lưu trữ | Local Filesystem (Multer + fs) |
2.2 Metadata Tệp tin & Thư mục
| Cột | Loại | Cho phép NULL | Mặc định | Mô tả |
| id | uuid | KHÔNG | gen_random_uuid() | Khóa chính |
| employer_id | uuid | CÓ | NULL | FK đến sys_employers(id), SET NULL khi xóa |
| user_id | uuid | KHÔNG | - | FK đến auth_users(id), CASCADE khi xóa |
| parent_folder_id | uuid | CÓ | NULL | FK đến sys_files(id), thư mục cha (NULL = gốc) |
| type | varchar(10) | KHÔNG | 'FILE' | Loại bản ghi: FILE hoặc FOLDER |
| original_name | varchar(500) | KHÔNG | - | Tên tệp tin gốc hoặc tên thư mục |
| file_path | varchar(1000) | CÓ | NULL | Đường dẫn tệp tin trên local filesystem (NULL cho thư mục) |
| mime_type | varchar(100) | CÓ | NULL | Loại MIME (NULL cho thư mục) |
| file_size | bigint | CÓ | 0 | Kích thước tệp tin bằng byte (0 cho thư mục) |
| category | varchar(50) | KHÔNG | 'OTHER' | Phân loại danh mục tệp tin |
| is_public | boolean | KHÔNG | FALSE | Tệp tin có được truy cập công khai không |
| status | varchar(20) | KHÔNG | 'ACTIVE' | Vòng đời trạng thái tệp tin |
| search_vector | tsvector | CÓ | NULL | Vector tìm kiếm toàn văn (chỉ tệp tin/thư mục) |
| created_at | timestamp | KHÔNG | NOW() | Thời gian tạo |
| updated_at | timestamp | KHÔNG | NOW() | Thời gian cập nhật lần cuối |
| deleted_at | timestamp | CÓ | NULL | Thời gian xóa mềm (NULL = đang hoạt động) |
CREATE TABLE sys_files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
employer_id UUID REFERENCES sys_employers(id) ON DELETE SET NULL,
user_id UUID NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE,
parent_folder_id UUID REFERENCES sys_files(id) ON DELETE SET NULL,
type VARCHAR(10) NOT NULL DEFAULT 'FILE',
original_name VARCHAR(500) NOT NULL,
file_path VARCHAR(1000),
mime_type VARCHAR(100),
file_size BIGINT DEFAULT 0,
category VARCHAR(50) NOT NULL DEFAULT 'OTHER',
is_public BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
search_vector TSVECTOR,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMP,
CONSTRAINT sys_files_type_check CHECK (type IN ('FILE', 'FOLDER')),
CONSTRAINT sys_files_category_check CHECK (category IN ('RESUME', 'AVATAR', 'COMPANY_DOC', 'JOB_ATTACHMENT', 'OTHER')),
CONSTRAINT sys_files_status_check CHECK (status IN ('TEMP', 'ACTIVE', 'DELETED', 'REPLACED'))
);
-- Thư mục: file_path, mime_type là NULL
-- Tệp tin: file_path, mime_type không NULL
2.3 Chiến lược Đường dẫn Lưu trữ
Các tệp tin được tổ chức trên local filesystem bằng cấu trúc thư mục phân cấp hỗ trợ đa tenancy và kiểm soát truy cập:
| Ngữ cảnh | Mẫu | Ví dụ |
| Theo phạm vi Tổ chức | uploads/{user_id}/{category}/{uuid_filename} | uploads/user-123/RESUME/a1b2c3d4-resume.pdf |
| Người dùng cá nhân | uploads/{user_id}/{category}/{uuid_filename} | uploads/user-456/AVATAR/e5f6g7h8-photo.jpg |
Các Thành phần Đường dẫn:
- Thư mục gốc uploads/: Thư mục gốc lưu trữ tất cả tệp tin
- Tiền tố UUID: Tên tệp tin được tạo để ngăn chặn xung đột (ví dụ:
a1b2c3d4-resume.pdf)
- Thư mục danh mục: Nhóm tệp tin theo mục đích (RESUME, AVATAR, v.v.)
- Thư mục Người dùng: Cách ly tệp tin theo từng user để kiểm soát truy cập
2.4 Giá trị Enum Danh mục
| Danh mục | Mô tả | Các loại MIME Thông thường | Kích thước Tối đa |
RESUME | Tệp tin hồ sơ/CV của người dùng | application/pdf, application/msword | 10 MB |
AVATAR | Ảnh hồ sơ người dùng | image/jpeg, image/png, image/webp | 5 MB |
COMPANY_DOC | Tài liệu công ty (logo, chứng nhận) | image/*, application/pdf | 10 MB |
JOB_ATTACHMENT | Đính kèm bài tuyển dụng | application/pdf, application/msword, image/* | 10 MB |
OTHER | Tệp tin chung/khác | * | 10 MB |
2.5 Vòng đời Trạng thái
| Trạng thái | Mô tả | Chuyển đổi |
TEMP | MultipartFile đã nhận, lưu vào filesystem | TEMP → ACTIVE (khi xác nhận), TEMP → DELETED (khi hết hạn/dọn dẹp) |
ACTIVE | Đã xác nhận tải lên, tệp tin sẵn sàng sử dụng | ACTIVE → DELETED (khi người dùng/hệ thống xóa), ACTIVE → REPLACED (tải lên CV) |
DELETED | Đã xóa mềm, đã xóa vật lý trên filesystem | DELETED → (trạng thái kết thúc, dọn dẹp vật lý bởi worker) |
REPLACED | Đã thay thế bởi phiên bản mới hơn (tải lên CV) | REPLACED → (trạng thái kết thúc, dọn dẹp vật lý bởi worker) |
2.6 Quyền truy cập Tệp tin
| Cột | Loại | Cho phép NULL | Mặc định | Mô tả |
| id | uuid | KHÔNG | gen_random_uuid() | Khóa chính |
| file_id | uuid | KHÔNG | - | FK đến sys_files(id), CASCADE khi xóa |
| user_id | uuid | CÓ | NULL | FK đến auth_users(id), quyền cấp người dùng |
| role | varchar(50) | CÓ | NULL | Quyền dựa trên vai trò (ADMIN, HR, MEMBER) |
| org_id | uuid | CÓ | NULL | Quyền trên toàn tổ chức |
| permission | varchar(20) | KHÔNG | - | Loại quyền: VIEW, DOWNLOAD, EDIT, DELETE |
| created_at | timestamp | KHÔNG | NOW() | Thời gian tạo |
| updated_at | timestamp | KHÔNG | NOW() | Thời gian cập nhật lần cuối |
CREATE TABLE sys_file_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_id UUID NOT NULL REFERENCES sys_files(id) ON DELETE CASCADE,
user_id UUID REFERENCES auth_users(id) ON DELETE CASCADE,
role VARCHAR(50),
org_id UUID,
permission VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT sys_file_permissions_permission_check CHECK (permission IN ('VIEW', 'DOWNLOAD', 'EDIT', 'DELETE')),
CONSTRAINT sys_file_permissions_target_check CHECK (
(user_id IS NOT NULL AND role IS NULL AND org_id IS NULL) OR
(user_id IS NULL AND role IS NOT NULL AND org_id IS NULL) OR
(user_id IS NULL AND role IS NULL AND org_id IS NOT NULL)
)
);
-- Một trong ba: user_id, role, hoặc org_id phải được đặt (không phải nhiều)
2.7 Thống kê Sử dụng Lưu trữ
| Cột | Loại | Cho phép NULL | Mặc định | Mô tả |
| id | uuid | KHÔNG | gen_random_uuid() | Khóa chính |
| user_id | uuid | CÓ | NULL | FK đến auth_users(id), sử dụng cấp người dùng |
| org_id | uuid | CÓ | NULL | Sử dụng cấp tổ chức |
| total_size | bigint | KHÔNG | 0 | Tổng dung lượng lưu trữ đã sử dụng bằng byte |
| file_count | integer | KHÔNG | 0 | Số lượng tệp tin |
| last_calculated_at | timestamp | KHÔNG | NOW() | Thời gian tính toán lần cuối |
CREATE TABLE sys_storage_usage (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth_users(id) ON DELETE CASCADE,
org_id UUID,
total_size BIGINT NOT NULL DEFAULT 0,
file_count INTEGER NOT NULL DEFAULT 0,
last_calculated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT sys_storage_usage_scope_check CHECK (
(user_id IS NOT NULL AND org_id IS NULL) OR
(user_id IS NULL AND org_id IS NOT NULL)
)
);
CREATE UNIQUE INDEX sys_storage_usage_user_idx ON sys_storage_usage(user_id) WHERE user_id IS NOT NULL;
CREATE UNIQUE INDEX sys_storage_usage_org_idx ON sys_storage_usage(org_id) WHERE org_id IS NOT NULL;
2.8 Hạn ngạch Lưu trữ
| Cột | Loại | Cho phép NULL | Mặc định | Mô tả |
| id | uuid | KHÔNG | gen_random_uuid() | Khóa chính |
| user_id | uuid | CÓ | NULL | FK đến auth_users(id), hạn ngạch cấp người dùng |
| org_id | uuid | CÓ | NULL | Hạn ngạch cấp tổ chức |
| max_total_size | bigint | KHÔNG | - | Tổng dung lượng tối đa bằng byte |
| max_file_count | integer | KHÔNG | - | Số lượng tệp tin tối đa |
| created_at | timestamp | KHÔNG | NOW() | Thời gian tạo |
| updated_at | timestamp | KHÔNG | NOW() | Thời gian cập nhật lần cuối |
CREATE TABLE sys_storage_quotas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth_users(id) ON DELETE CASCADE,
org_id UUID,
max_total_size BIGINT NOT NULL,
max_file_count INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT sys_storage_quotas_scope_check CHECK (
(user_id IS NOT NULL AND org_id IS NULL) OR
(user_id IS NULL AND org_id IS NOT NULL)
)
);
CREATE UNIQUE INDEX sys_storage_quotas_user_idx ON sys_storage_quotas(user_id) WHERE user_id IS NOT NULL;
CREATE UNIQUE INDEX sys_storage_quotas_org_idx ON sys_storage_quotas(org_id) WHERE org_id IS NOT NULL;
2.9 Chính sách Lưu trữ
| Cột | Loại | Cho phép NULL | Mặc định | Mô tả |
| id | uuid | KHÔNG | gen_random_uuid() | Khóa chính |
| category | varchar(50) | KHÔNG | - | Danh mục tệp tin (duy nhất) |
| allowed_mime_types | jsonb | KHÔNG | - | Mảng các loại MIME được phép |
| max_file_size | bigint | KHÔNG | - | Kích thước tệp tin tối đa bằng byte |
| retention_days | integer | CÓ | NULL | Thời gian lưu trữ bằng ngày (NULL = vĩnh viễn) |
| encryption_enabled | boolean | KHÔNG | FALSE | Bật mã hóa phía server |
| created_at | timestamp | KHÔNG | NOW() | Thời gian tạo |
| updated_at | timestamp | KHÔNG | NOW() | Thời gian cập nhật lần cuối |
CREATE TABLE sys_storage_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category VARCHAR(50) NOT NULL UNIQUE,
allowed_mime_types JSONB NOT NULL,
max_file_size BIGINT NOT NULL,
retention_days INTEGER,
encryption_enabled BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Chính sách mặc định
INSERT INTO sys_storage_policies (category, allowed_mime_types, max_file_size) VALUES
('RESUME', '["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]', 10485760),
('AVATAR', '["image/jpeg", "image/png", "image/webp"]', 5242880),
('COMPANY_DOC', '["image/*", "application/pdf"]', 10485760),
('JOB_ATTACHMENT', '["application/pdf", "application/msword", "image/*"]', 10485760),
('OTHER', '["*/*"]', 10485760);
3. Chỉ mục
-- sys_files
CREATE INDEX sys_files_user_id_idx ON sys_files(user_id);
CREATE INDEX sys_files_employer_id_idx ON sys_files(employer_id);
CREATE INDEX sys_files_category_idx ON sys_files(category);
CREATE INDEX sys_files_status_idx ON sys_files(status);
CREATE INDEX sys_files_created_at_idx ON sys_files(created_at);
CREATE INDEX sys_files_type_idx ON sys_files(type);
CREATE INDEX sys_files_parent_folder_id_idx ON sys_files(parent_folder_id);
-- Chỉ mục tổng hợp cho mẫu truy vấn phổ biến (liệt kê tệp tin đang hoạt động theo người dùng và danh mục)
CREATE INDEX sys_files_user_category_status_idx ON sys_files(user_id, category, status);
-- Chỉ mục tổng hợp cho truy vấn theo phạm vi nhà tuyển dụng
CREATE INDEX sys_files_employer_category_status_idx ON sys_files(employer_id, category, status);
-- Chỉ mục tổng hợp cho liệt kê thư mục (các mục con của một thư mục)
CREATE INDEX sys_files_parent_folder_type_idx ON sys_files(parent_folder_id, type, status);
-- Chỉ mục tìm kiếm toàn văn
CREATE INDEX sys_files_search_idx ON sys_files USING gin(search_vector);
-- Ràng buộc duy nhất trên file_path để ngăn trùng lặp (chỉ cho tệp tin)
CREATE UNIQUE INDEX sys_files_file_path_unique ON sys_files(file_path) WHERE file_path IS NOT NULL;
-- sys_file_permissions
CREATE INDEX sys_file_permissions_file_id_idx ON sys_file_permissions(file_id);
CREATE INDEX sys_file_permissions_user_id_idx ON sys_file_permissions(user_id);
CREATE INDEX sys_file_permissions_role_idx ON sys_file_permissions(role);
CREATE INDEX sys_file_permissions_org_id_idx ON sys_file_permissions(org_id);
-- sys_storage_usage
CREATE INDEX sys_storage_usage_user_id_idx ON sys_storage_usage(user_id);
CREATE INDEX sys_storage_usage_org_id_idx ON sys_storage_usage(org_id);
-- sys_storage_quotas
CREATE INDEX sys_storage_quotas_user_id_idx ON sys_storage_quotas(user_id);
CREATE INDEX sys_storage_quotas_org_id_idx ON sys_storage_quotas(org_id);
Mẫu Sử dụng Chỉ mục
| Mẫu Truy vấn | Chỉ mục Sử dụng | dòng Ước tính |
| Liệt kê CV của người dùng | sys_files_user_category_status_idx | 5-20 |
| Liệt kê tài liệu nhà tuyển dụng | sys_files_employer_category_status_idx | 10-50 |
| Tìm tệp tin theo ID | Khóa chính (id) | 1 |
| Dọn dẹp các tệp tin TEMP cũ | sys_files_status_idx + sys_files_created_at_idx | 0-100 |
| Liệt kê các tải lên gần đây | sys_files_created_at_idx | thay đổi |
4. Di chuyển Dữ liệu
4.1 Chiến lược Di chuyển
| Khía cạnh | Quyết định |
| Công cụ | Drizzle ORM (drizzle-kit) |
| Cơ sở dữ liệu | file_db |
| Hoàn nguyên | Bắt buộc cho mỗi bản di chuyển |
4.2 Thứ tự Di chuyển
| Thứ tự | Di chuyển | Mô tả |
| 1 | 001_create_sys_files | Tạo bảng sys_files với ràng buộc |
| 2 | 002_create_indexes | Tạo tất cả chỉ mục |
| 3 | 003_add_folder_support | Thêm các cột parent_folder_id, type, search_vector vào sys_files |
| 4 | 004_create_sys_file_permissions | Tạo bảng sys_file_permissions |
| 5 | 005_create_sys_storage_usage | Tạo bảng sys_storage_usage |
| 6 | 006_create_sys_storage_quotas | Tạo bảng sys_storage_quotas |
| 7 | 007_create_sys_storage_policies | Tạo bảng sys_storage_policies với dữ liệu mặc định |
5. Từ điển Dữ liệu
| Bảng | Mô tả | Giải quyết | dòng Ước tính (Năm 1) |
| sys_files | Metadata tệp tin và thư mục, tham chiếu lưu trữ | FR-001 - FR-013 | 150,000 |
| sys_file_permissions | Quyền truy cập chi tiết cho mỗi tệp tin/thư mục | FR-014 | 50,000 |
| sys_storage_usage | Thống kê sử dụng lưu trữ theo người dùng/tổ chức | FR-016 | 10,000 |
| sys_storage_quotas | Cấu hình hạn ngạch lưu trữ theo người dùng/tổ chức | FR-017 | 5,000 |
| sys_storage_policies | Chính sách lưu trữ theo danh mục tệp tin | FR-018 | 5 |
Ước tính Tăng trưởng Dòng
| Danh mục Tệp tin | Kích thước Trung bình (byte) | Ước tính Dòng/Năm 1 | Lưu trữ (GB) |
| RESUME | 500 KB | 20,000 | 10 |
| AVATAR | 200 KB | 15,000 | 3 |
| COMPANY_DOC | 1 MB | 5,000 | 5 |
| JOB_ATTACHMENT | 800 KB | 10,000 | 8 |
| OTHER | 500 KB | 5,000 | 2.5 |
| Tổng cộng | - | 55,000 | 28.5 |