1. 12:10 13/08/2026
CHUỖI THỰC HÀNH: CHATBOT AI HỎI ĐÁP ĐẤT ĐAI — PHP 8.x + MySQL 9.x
Lưu ý trách nhiệm: đây là phần mềm cung cấp thông tin tham khảo từ kho tài liệu đã được quản trị viên phê duyệt; không phải tư vấn pháp lý, không xác nhận quyền sử dụng đất, không thay thế hồ sơ/cơ quan có thẩm quyền. Không yêu cầu người dùng gửi CCCD, số sổ, ảnh giấy tờ, tọa độ chính xác hay dữ liệu cá nhân vào ô chat. Khi trả lời có ảnh hưởng quyền/lợi ích cụ thể, chatbot phải hướng người dùng kiểm tra văn bản gốc và liên hệ cơ quan có thẩm quyền hoặc chuyên gia pháp lý.
# 02/10 — MySQL 9.x: tạo database và schema đầy đủ
Bài này tạo schema độc lập. Thiết kế ưu tiên: tách **tài liệu** khỏi **đoạn trích** để tìm kiếm; chỉ lưu lượng lịch sử tối thiểu; IP được băm HMAC (không lưu IP thô); mọi sự kiện quan trọng có audit log. Hãy dùng tài khoản database riêng, không dùng `root` cho website.
## 1. Tạo database và user ứng dụng
Đăng nhập MySQL với tài khoản quản trị, sau đó chạy (đổi mật khẩu ví dụ bằng mật khẩu mạnh của bạn):
```sql
CREATE DATABASE land_ai_chatbot
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'land_chat'@'127.0.0.1' IDENTIFIED BY 'CHANGE_THIS_DATABASE_PASSWORD';
GRANT SELECT, INSERT, UPDATE, DELETE ON land_ai_chatbot.* TO 'land_chat'@'127.0.0.1';
FLUSH PRIVILEGES;
```
Nếu PHP kết nối qua `localhost` thay vì `127.0.0.1`, cấp đúng host tương ứng. Trong production, giới hạn user DB đúng hostname/IP của web server và không cấp `GRANT OPTION`, `FILE`, `SUPER` hay quyền tạo user.
## 2. Tạo `database/schema.sql`
Copy toàn bộ nội dung sau vào tệp `database/schema.sql`:
```sql
USE land_ai_chatbot;
CREATE TABLE knowledge_documents (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
source_url VARCHAR(2048) NOT NULL,
source_owner VARCHAR(255) NOT NULL,
source_checked_at DATE NOT NULL,
status ENUM('draft','published','archived') NOT NULL DEFAULT 'draft',
content_hash CHAR(64) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_knowledge_documents_hash (content_hash),
KEY idx_knowledge_documents_status_checked (status, source_checked_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE knowledge_chunks (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
document_id BIGINT UNSIGNED NOT NULL,
chunk_no INT UNSIGNED NOT NULL,
content MEDIUMTEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_knowledge_chunks_document_no (document_id, chunk_no),
KEY idx_knowledge_chunks_document (document_id),
FULLTEXT KEY ft_knowledge_chunks_content (content),
CONSTRAINT fk_chunks_document
FOREIGN KEY (document_id) REFERENCES knowledge_documents(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE chat_sessions (
id CHAR(36) NOT NULL,
ip_hmac CHAR(64) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_seen_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_chat_sessions_last_seen (last_seen_at),
KEY idx_chat_sessions_ip_hmac (ip_hmac)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE chat_messages (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
session_id CHAR(36) NOT NULL,
role ENUM('user','assistant') NOT NULL,
content TEXT NOT NULL,
source_ids_json JSON NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_chat_messages_session_created (session_id, created_at),
CONSTRAINT fk_messages_session
FOREIGN KEY (session_id) REFERENCES chat_sessions(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE audit_events (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
event_type VARCHAR(80) NOT NULL,
request_id CHAR(36) NOT NULL,
session_id CHAR(36) NULL,
details_json JSON NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_audit_events_type_created (event_type, created_at),
KEY idx_audit_events_request (request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
## 3. Chạy schema và kiểm tra
Trong terminal ở thư mục dự án:
```bash
mysql -u land_chat -p land_ai_chatbot < database/schema.sql
mysql -u land_chat -p -e "SHOW TABLES FROM land_ai_chatbot;"
```
Bạn phải thấy 5 bảng: `knowledge_documents`, `knowledge_chunks`, `chat_sessions`, `chat_messages`, `audit_events`.
## Vì sao cần các cột này?
- `source_checked_at` buộc quy trình nhập liệu biết lần cuối ai đã kiểm tra nguồn. Đừng để chatbot dùng nguồn không rõ ngày.
- `status` là “cổng an toàn”: truy vấn chỉ lấy `published`; tài liệu mới nhập phải là `draft` cho tới khi được duyệt.
- `content_hash` tránh import trùng tài liệu bằng SHA-256.
- `FULLTEXT` giúp MySQL tìm các đoạn liên quan thay vì đẩy toàn bộ kho tri thức vào prompt.
- `source_ids_json` giúp hiển thị các nguồn của đúng câu trả lời, thuận tiện audit sau này.
- `ip_hmac` dùng `hash_hmac()` ở PHP với APP_KEY; không lấy nó làm định danh hay bảo mật tuyệt đối, chỉ phục vụ rate limit/audit tối thiểu.
Không ghi khóa AI vào bất kỳ bảng nào. Khóa chỉ ở biến môi trường `.env` trên máy chủ, sẽ cấu hình ở bài 03.