1. Tổng quan về Công nghệ
-
Backend: Python & Flask
- Flask: Một framework web nhẹ, dùng để xử lý yêu cầu HTTP, định tuyến và hiển thị template.
- Thư viện kết nối cơ sở dữ liệu: Ví dụ:
psycopg2(cho PostgreSQL),mysql-connector-python(cho MySQL), hoặcsqlite3(cho SQLite). Dùng để thực thi trực tiếp các câu lệnh SQL. - JSON: Định dạng dữ liệu được sử dụng để truyền kết quả truy vấn từ backend về frontend.
-
Frontend: HTML, CSS & JavaScript
- Bootstrap: Cung cấp các thành phần giao diện, bố cục và style (ví dụ: form, bảng, thanh điều hướng).
- Jinja2: Công cụ render template tích hợp trong Flask, dùng để tạo trang HTML.
- JavaScript: Sử dụng để gửi các yêu cầu AJAX (Asynchronous JavaScript and XML) tới backend và cập nhật kết quả truy vấn trên trang một cách động.
2. Chi tiết Cách triển khai
2.1. Backend: Flask kết hợp SQL truyền thống
-
Quản lý kết nối cơ sở dữ liệu:
- Trong ứng dụng Flask, bạn có thể tạo một hàm tiện ích để quản lý kết nối. Ví dụ, mở kết nối khi bắt đầu mỗi yêu cầu và đóng lại khi kết thúc, hoặc sử dụng đối tượng
gđể lưu trữ kết nối, đảm bảo chỉ một kết nối được sử dụng trong suốt vòng đời của yêu cầu.
- Trong ứng dụng Flask, bạn có thể tạo một hàm tiện ích để quản lý kết nối. Ví dụ, mở kết nối khi bắt đầu mỗi yêu cầu và đóng lại khi kết thúc, hoặc sử dụng đối tượng
-
Xử lý các tuyến (routes) cho yêu cầu truy vấn:
- Tạo các tuyến (routes) riêng biệt cho từng loại truy vấn (ví dụ: danh sách người dùng, thông tin bán hàng). Ví dụ:
/api/users,/api/sales. - Các tuyến này sẽ nhận tham số truy vấn từ frontend (ví dụ: khoảng ngày, tên người dùng).
- Tạo các tuyến (routes) riêng biệt cho từng loại truy vấn (ví dụ: danh sách người dùng, thông tin bán hàng). Ví dụ:
-
Thực thi truy vấn SQL:
- Trong hàm xử lý của mỗi tuyến, dựa trên các tham số nhận được, xây dựng câu lệnh SQL tương ứng. Lưu ý: Để phòng tránh tấn công SQL Injection, hãy luôn sử dụng truy vấn có tham số (Parameterized Queries) thay vì ghép chuỗi trực tiếp.
- Thực thi câu lệnh SQL và lấy kết quả.
- Chuyển đổi kết quả truy vấn (thường là danh sách các tuple hoặc dictionary) sang định dạng JSON và trả về cho frontend.
Ví dụ mã Backend (Mã giả):
from flask import Flask, jsonify, request
import psycopg2 # Ví dụ với PostgreSQL
import os
app = Flask(__name__)
# Hàm quản lý kết nối đơn giản
def get_db_connection():
conn = psycopg2.connect(os.environ['DATABASE_URL'])
return conn
@app.route('/api/users')
def fetch_users():
db_conn = get_db_connection()
db_cursor = db_conn.cursor()
# Lấy tham số từ request, ví dụ: username
user_filter = request.args.get('username', '')
# Sử dụng truy vấn có tham số để ngăn chặn SQL Injection
sql_statement = "SELECT user_id, full_name, email_address FROM app_users WHERE full_name LIKE %s;"
db_cursor.execute(sql_statement, (f"%{user_filter}%",))
user_data = db_cursor.fetchall()
db_conn.close()
# Chuyển đổi kết quả thành danh sách các dictionary và trả về JSON
user_list_formatted = [{"id": row[0], "name": row[1], "email": row[2]} for row in user_data]
return jsonify(user_list_formatted)
# Các tuyến truy vấn khác (/api/sales, /api/products, v.v.) được triển khai tương tự
2.2. Frontend: Bootstrap & AJAX
-
Bố cục trang chính:
- Sử dụng các thành phần của Bootstrap để thiết kế giao diện trên trang HTML chính.
- Dùng khu vực Form (
<form>) để chứa các điều kiện truy vấn khác nhau. - Sử dụng Thanh điều hướng (Navs) hoặc Tab của Bootstrap để chuyển đổi giữa các loại truy vấn.
- Sử dụng một phần tử
<table>trống hoặc một container<div>làm nơi hiển thị kết quả truy vấn.
-
Logic JavaScript:
- Thêm trình lắng nghe sự kiện (event listener) cho từng form hoặc nút truy vấn.
- Khi người dùng gửi form hoặc nhấp vào nút truy vấn, ngăn chặn hành vi gửi form mặc định.
- Thu thập các tham số truy vấn từ form.
- Sử dụng Fetch API hoặc
jQuery.ajax()để gửi yêu cầuGETkhông đồng bộ tới tuyến API tương ứng của backend, đính kèm các tham số truy vấn vào URL. - Khi nhận được dữ liệu JSON từ backend, JavaScript sẽ phân tích cú pháp dữ liệu đó.
- Tạo động các dòng bảng (
<tr>), điền dữ liệu vào bảng và thay thế kết quả cũ.
Ví dụ mã Frontend (Mã giả):
<div class="container mt-4">
<h2>Trung tâm Truy vấn Dữ liệu</h2>
<ul class="nav nav-tabs" id="dataQueryTabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#user-query-pane">Truy vấn Người dùng</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#sales-query-pane">Truy vấn Bán hàng</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="user-query-pane">
<form id="userSearchForm" class="mt-3">
<div class="mb-3">
<input type="text" class="form-control" id="userNameInput" placeholder="Nhập tên người dùng">
</div>
<button type="submit" class="btn btn-primary">Tìm kiếm Người dùng</button>
</form>
<table class="table table-striped table-hover mt-4">
<thead>
<tr>
<th scope="col">Mã</th>
<th scope="col">Tên</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody id="userResultTableBody">
<!-- Dữ liệu người dùng sẽ được tải vào đây -->
</tbody>
</table>
</div>
<!-- Pane cho Truy vấn Bán hàng sẽ được thêm vào đây -->
</div>
</div>
<script>
document.getElementById('userSearchForm').addEventListener('submit', function(event) {
event.preventDefault(); // Ngăn chặn hành vi submit mặc định của form
const userNameToFind = document.getElementById('userNameInput').value.trim();
// Xây dựng URL với tham số
const apiUrl = `/api/users?username=${encodeURIComponent(userNameToFind)}`;
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // Chuyển đổi response thành JSON
})
.then(userDataList => {
const tableBodyElement = document.getElementById('userResultTableBody');
tableBodyElement.innerHTML = ''; // Xóa dữ liệu cũ trước khi thêm mới
if (userDataList.length === 0) {
tableBodyElement.innerHTML = '<tr><td colspan="3">Không tìm thấy người dùng nào.</td></tr>';
return;
}
// Duyệt qua danh sách dữ liệu và tạo các dòng bảng
userDataList.forEach(user => {
const tableRow = `
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.email}</td>
</tr>
`;
tableBodyElement.innerHTML += tableRow; // Thêm dòng mới vào bảng
});
})
.catch(error => {
console.error('Lỗi khi truy vấn dữ liệu người dùng:', error);
const tableBodyElement = document.getElementById('userResultTableBody');
tableBodyElement.innerHTML = `<tr><td colspan="3">Đã xảy ra lỗi khi tải dữ liệu.</td></tr>`;
});
});
</script>
Mô hình phát triển tách biệt giữa frontend và backend, trao đổi dữ liệu qua API này, đáp ứng tốt yêu cầu về một ứng dụng Single-Page (SPA) và tận dụng tối đa sự gọn nhẹ của Flask cùng tính dễ sử dụng của Bootstrap.