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.