- vừa được xem lúc

THIẾT KẾ DATABASE CHO MÔI TRƯỜNG HỌC SINH

0 0 2

Người đăng: phạm tuấn

Theo Viblo Asia

// Cấu hình API const API_CONFIG = { baseUrl: 'https://jsonplaceholder.typicode.com', endpoints: { getUrls: '/posts' } };

// Cấu hình IndexedDB const DB_NAME = 'urlCrawlerDB'; const DB_VERSION = 1; const STORES = { SENT_URLS: 'sentUrls', CRAWLED_URLS: 'crawledUrls', PARENT_URLS: 'parentUrls' };

// Khởi tạo IndexedDB let db;

function initDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION);

request.onerror = (event) => { console.error('Lỗi khi mở cơ sở dữ liệu:', event.target.error); reject(event.target.error);
}; request.onsuccess = (event) => { db = event.target.result; resolve(db);
}; request.onupgradeneeded = (event) => { const db = event.target.result; // Tạo các kho lưu trữ đối tượng if (!db.objectStoreNames.contains(STORES.SENT_URLS)) { db.createObjectStore(STORES.SENT_URLS, { keyPath: 'id', autoIncrement: true }); } if (!db.objectStoreNames.contains(STORES.CRAWLED_URLS)) { db.createObjectStore(STORES.CRAWLED_URLS, { keyPath: 'parentId' }); } if (!db.objectStoreNames.contains(STORES.PARENT_URLS)) { db.createObjectStore(STORES.PARENT_URLS, { keyPath: 'id' }); }
};

}); }

// --- Quản lý Cache URL đã gửi --- async function getSentUrlsCache() { try { const transaction = db.transaction([STORES.SENT_URLS], 'readonly'); const store = transaction.objectStore(STORES.SENT_URLS); const request = store.getAll();

return new Promise((resolve, reject) => { request.onsuccess = () => { const urls = request.result.map(item => item.url); resolve({ urls, lastUpdated: new Date().toISOString() }); }; request.onerror = () => reject(request.error);
});

} catch (e) { console.error('Lỗi khi lấy cache URL đã gửi:', e); return { urls: [], lastUpdated: null }; } }

async function setSentUrlsCache(urls) { try { const transaction = db.transaction([STORES.SENT_URLS], 'readwrite'); const store = transaction.objectStore(STORES.SENT_URLS);

// Xóa dữ liệu hiện có
await new Promise((resolve, reject) => { const clearRequest = store.clear(); clearRequest.onsuccess = resolve; clearRequest.onerror = reject;
}); // Thêm URL mới
const uniqueUrls = Array.from(new Set(urls));
for (const url of uniqueUrls) { await new Promise((resolve, reject) => { const addRequest = store.add({ url, timestamp: new Date().toISOString() }); addRequest.onsuccess = resolve; addRequest.onerror = reject; });
} console.log(`✅ Đã lưu ${urls.length} URLs vào cache gửi đi.`);

} catch (e) { console.error('❌ Lỗi khi lưu cache URL đã gửi:', e); } }

async function addSentUrlsToCache(newUrls) { const currentCache = await getSentUrlsCache(); const updatedUrls = [...currentCache.urls, ...newUrls]; await setSentUrlsCache(updatedUrls); }

async function clearSentUrlsCacheIfOld() { try { const cacheData = await getSentUrlsCache(); if (cacheData && cacheData.lastUpdated) { const lastUpdatedDate = new Date(cacheData.lastUpdated); const today = new Date();

 if (lastUpdatedDate.toDateString() !== today.toDateString()) { console.log('🗓️ Cache URL đã gửi cũ, đang xóa...'); const transaction = db.transaction([STORES.SENT_URLS], 'readwrite'); const store = transaction.objectStore(STORES.SENT_URLS); await new Promise((resolve, reject) => { const clearRequest = store.clear(); clearRequest.onsuccess = resolve; clearRequest.onerror = reject; }); console.log('✅ Đã xóa cache URL đã gửi cũ.'); return true; }
}

} catch (e) { console.error('Lỗi khi kiểm tra/xóa cache cũ:', e); } return false; }

// --- Quản lý Cache URL con đã crawl theo Parent ID --- async function getCrawledUrlsByParentIdCache() { try { const transaction = db.transaction([STORES.CRAWLED_URLS], 'readonly'); const store = transaction.objectStore(STORES.CRAWLED_URLS); const request = store.getAll();

return new Promise((resolve, reject) => { request.onsuccess = () => { const urlsByParentId = {}; request.result.forEach(item => { urlsByParentId[item.parentId] = new Set(item.urls); }); resolve({ urlsByParentId, lastUpdated: new Date().toISOString() }); }; request.onerror = () => reject(request.error);
});

} catch (e) { console.error('Lỗi khi lấy cache URL đã crawl theo parent ID:', e); return { urlsByParentId: {}, lastUpdated: null }; } }

async function setCrawledUrlsByParentIdCache(cacheData) { try { const transaction = db.transaction([STORES.CRAWLED_URLS], 'readwrite'); const store = transaction.objectStore(STORES.CRAWLED_URLS);

// Clear existing data
await new Promise((resolve, reject) => { const clearRequest = store.clear(); clearRequest.onsuccess = resolve; clearRequest.onerror = reject;
}); // Add new data
for (const parentId in cacheData.urlsByParentId) { await new Promise((resolve, reject) => { const addRequest = store.add({ parentId, urls: Array.from(cacheData.urlsByParentId[parentId]), timestamp: new Date().toISOString() }); addRequest.onsuccess = resolve; addRequest.onerror = reject; });
} console.log(`✅ Đã lưu cache URL đã crawl theo parent ID.`);

} catch (e) { console.error('❌ Lỗi khi lưu cache URL đã crawl theo parent ID:', e); } }

async function addCrawledUrlsForParentToCache(parentId, newUrls) { const currentCache = await getCrawledUrlsByParentIdCache(); if (!currentCache.urlsByParentId[parentId]) { currentCache.urlsByParentId[parentId] = new Set(); } newUrls.forEach(url => currentCache.urlsByParentId[parentId].add(url)); await setCrawledUrlsByParentIdCache(currentCache); }

async function clearCrawledUrlsByParentIdCacheIfOld() { try { const cacheData = await getCrawledUrlsByParentIdCache(); if (cacheData && cacheData.lastUpdated) { const lastUpdatedDate = new Date(cacheData.lastUpdated); const today = new Date();

 if (lastUpdatedDate.toDateString() !== today.toDateString()) { console.log('🗓️ Cache URL đã crawl theo parent ID cũ, đang xóa...'); const transaction = db.transaction([STORES.CRAWLED_URLS], 'readwrite'); const store = transaction.objectStore(STORES.CRAWLED_URLS); await new Promise((resolve, reject) => { const clearRequest = store.clear(); clearRequest.onsuccess = resolve; clearRequest.onerror = reject; }); console.log('✅ Đã xóa cache URL đã crawl theo parent ID cũ.'); return true; }
}

} catch (e) { console.error('Lỗi khi kiểm tra/xóa cache cũ:', e); } return false; }

// --- Parent URL Management --- async function getParentFromCache(id) { try { const transaction = db.transaction([STORES.PARENT_URLS], 'readonly'); const store = transaction.objectStore(STORES.PARENT_URLS); const request = store.get(id);

return new Promise((resolve, reject) => { request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error);
});

} catch (e) { console.error('Error getting parent from cache:', e); return null; } }

async function updateParentCache(id, childUrls) { try { const parentData = await getParentFromCache(id); if (parentData) { parentData.childUrls = [...new Set([...parentData.childUrls, ...childUrls])]; const transaction = db.transaction([STORES.PARENT_URLS], 'readwrite'); const store = transaction.objectStore(STORES.PARENT_URLS); await new Promise((resolve, reject) => { const putRequest = store.put(parentData); putRequest.onsuccess = resolve; putRequest.onerror = reject; }); } } catch (error) { console.error('Error updating parent cache:', error); } }

// Khởi tạo cơ sở dữ liệu khi extension được tải document.addEventListener('DOMContentLoaded', async () => { try { await initDB(); console.log('✅ Đã khởi tạo IndexedDB thành công'); } catch (error) { console.error('❌ Không thể khởi tạo IndexedDB:', error); } });

async function fetchUrlsFromApi() { const apiUrl = document.getElementById('apiUrl').value.trim(); const output = document.getElementById("output");

if (!apiUrl) { throw new Error('Vui lòng nhập URL API'); }

try { const response = await fetch(apiUrl); if (!response.ok) { throw new Error('Không thể kết nối đến API'); } const data = await response.json();

if (!Array.isArray(data)) { throw new Error('API phải trả về một mảng các object');
} const validUrls = data.filter(item => { try { if (!item.url || !item.id) return false; new URL(item.url); return true; } catch { return false; }
}); if (validUrls.length === 0) { throw new Error('Không tìm thấy URL hợp lệ nào trong dữ liệu API');
} // Lưu thông tin parent vào IndexedDB
for (const item of validUrls) { const transaction = db.transaction([STORES.PARENT_URLS], 'readwrite'); const store = transaction.objectStore(STORES.PARENT_URLS); await store.put({ id: item.id, url: item.url, childUrls: [] });
} return validUrls.map(item => item.url);

} catch (error) { console.error('Lỗi khi lấy URL từ API:', error); throw new Error(Lỗi khi lấy dữ liệu từ API: ${error.message}); } }

async function extractLinksFromUrls(urls) { const output = document.getElementById("output"); output.textContent = "🚀 Đang xử lý...";

const allLinks = [];
const processedIds = new Set(); for (const parentUrl of urls) { const parentData = Object.values(JSON.parse(localStorage.getItem('urlToIdMap') || '{}')) .find(data => data.url === parentUrl); if (!parentData || processedIds.has(parentData.id)) { console.log(`Bỏ qua URL ${parentUrl} vì đã xử lý ID ${parentData?.id}`); continue; } await new Promise((resolve) => { chrome.tabs.create({ url: parentUrl, active: false }, (tab) => { const tabId = tab.id; const listener = (updatedTabId, changeInfo) => { if (updatedTabId === tabId && changeInfo.status === "complete") { chrome.scripting.executeScript({ target: { tabId }, func: () => { const links = []; let titles = document.querySelectorAll('.pr-title.js__card-title'); if (titles.length === 0) { const selectors = [ ".product-item-top a[href]", ".product-item a[href]", ".item-title a[href]", ".post-title a[href]", "article a[href]", ".content-wrapper a[href]" ]; for (const selector of selectors) { titles = document.querySelectorAll(selector); if (titles.length > 0) break; } if (titles.length === 0) { const allLinks = document.querySelectorAll('a[href]'); titles = Array.from(allLinks).filter(link => { const href = link.href.toLowerCase(); return (href.includes('/product/') || href.includes('/item/') || href.includes('/post/') || href.includes('/detail/') || href.match(/\d{4,}/)); }); } } titles = Array.from(titles).filter(title => { let element = title; while (element) { if (element.classList && Array.from(element.classList).some(cls => cls.includes('footer'))) { return false; } element = element.parentElement; } return true; }); titles.forEach((title) => { const link = title.closest('a') || title; if (link && link.href && !link.href.includes('#') && !link.href.includes('javascript:') && link.href.startsWith('http')) { links.push(link.href); } }); return Array.from(new Set(links)); } }).then((results) => { const links = results[0].result || []; updateParentCache(parentData.id, links); allLinks.push(...links); if (parentData.id) processedIds.add(parentData.id); chrome.tabs.remove(tabId); chrome.tabs.onUpdated.removeListener(listener); resolve(); }).catch((err) => { console.error("❌ Lỗi:", err); chrome.tabs.remove(tabId); chrome.tabs.onUpdated.removeListener(listener); resolve(); }); } }; chrome.tabs.onUpdated.addListener(listener); }); });
} return allLinks;

}

async function sendDataToApi(links) { const submitApiUrl = document.getElementById('submitApiUrl').value.trim(); const apiMethod = document.getElementById('apiMethod').value; const output = document.getElementById("output");

if (!submitApiUrl) { throw new Error('Vui lòng nhập URL API gửi kết quả');
} try { const urlToIdMap = JSON.parse(localStorage.getItem('urlToIdMap') || '{}'); console.log('URL to id Map:', urlToIdMap); const requestData = { data: links.map(url => ({ url: url, id: urlToIdMap[url] || null })) }; console.log('Request Data:', requestData); const response = await fetch(submitApiUrl, { method: apiMethod, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }); if (!response.ok) { throw new Error('Không thể gửi dữ liệu lên API'); } const result = await response.json(); return result;
} catch (error) { console.error('Error sending data to API:', error); throw new Error(`Lỗi khi gửi dữ liệu lên API: ${error.message}`);
}

}

async function sendLinksToBackend(links, endpoint, method) { const output = document.getElementById("output"); if (!endpoint) { output.textContent = '❌ Vui lòng nhập URL API gửi kết quả.'; return; } try { const parentIds = new Set(); links.forEach(url => { const parentData = Object.values(JSON.parse(localStorage.getItem('urlToIdMap') || '{}')) .find(data => data.childUrls?.includes(url)); if (parentData) parentIds.add(parentData.id); });

 const data = Array.from(parentIds).map(id => { const parentData = getParentFromCache(id); return { id: id, urls: parentData?.childUrls || [] }; }); const payload = { data }; console.log('payload gửi backend:', payload); output.textContent = '🔄 Đang gửi dữ liệu về backend...'; const response = await fetch(endpoint, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error('Không thể gửi dữ liệu lên API'); } const result = await response.json(); output.textContent = `✅ Đã gửi dữ liệu về backend thành công!`; return result;
} catch (error) { output.textContent = `❌ Lỗi: ${error.message}`;
}

}

document.getElementById("fetchFromApi").addEventListener("click", async () => { const output = document.getElementById("output"); output.textContent = "🔄 Đang lấy danh sách URL từ API..."; try { // Xóa cache cũ nếu đã qua ngày clearCrawledUrlsByParentIdCacheIfOld();

 const apiUrl = document.getElementById('apiUrl').value.trim(); const submitApiUrl = document.getElementById('submitApiUrl').value.trim(); const apiMethod = document.getElementById('apiMethod').value; const filterToday = document.getElementById('filterToday').checked; const dataFromApi = await fetch(apiUrl); const urlObjs = await dataFromApi.json(); if (!Array.isArray(urlObjs) || urlObjs.length === 0) { output.textContent = "⚠️ Không tìm thấy URL nào từ API."; return; } // Lấy toàn bộ cache URL con theo parent ID const allCrawledUrlsCache = getCrawledUrlsByParentIdCache().urlsByParentId; console.log(`📊 Tổng số parent ID có cache: ${Object.keys(allCrawledUrlsCache).length}`); const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); const results = []; // Kết quả để gửi lên API for (const obj of urlObjs) { const { id, url } = obj; output.textContent = `🔄 Đang crawl: ${url}`; // Lấy cache URL con cho ID gốc hiện tại const currentParentCrawledUrls = allCrawledUrlsCache[id] || new Set(); console.log(`📊 Cache cho parent ID ${id}: ${currentParentCrawledUrls.size} URLs`); await new Promise((resolve) => { chrome.tabs.update(tab.id, {url}, () => { chrome.tabs.onUpdated.addListener(function listener(tabId, info) { if (tabId === tab.id && info.status === "complete") { chrome.tabs.onUpdated.removeListener(listener); // Gửi cache URL con CHỈ cho parent ID hiện tại chrome.tabs.sendMessage(tab.id, { action: "START_CRAWL", id, filterToday, crawledUrlsCache: Array.from(currentParentCrawledUrls) // Gửi mảng để serialize }, (response) => { if (response && response.success) { // response.links là mảng các URL con MỚI được tìm thấy cho parent ID này if (response.links && response.links.length > 0) { results.push({ id, urls: response.links }); // Thêm các URL con mới tìm được vào cache cho parent ID hiện tại addCrawledUrlsForParentToCache(id, response.links); } if (response.stoppedEarly) { console.log(`🚩 Dừng crawl sớm cho ${url} (ID: ${id}) do tìm thấy URL đã có trong cache của ID này.`); } } else if (response && response.error) { console.error(`❌ Lỗi crawl ${url} (ID: ${id}): ${response.error}`); } resolve(); }); } }); }); }); } // Tiếp tục gửi dữ liệu đến backend như bình thường if (results.length > 0) { const payload = { data: results }; output.textContent = '🔄 Đang gửi dữ liệu về backend...'; const response = await fetch(submitApiUrl, { method: apiMethod, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error('Không thể gửi dữ liệu lên API'); } output.textContent = `✅ Đã gửi dữ liệu về backend thành công!`; } else { output.textContent = '⚠️ Không có URL mới nào để gửi lên API.'; } } catch (error) { output.textContent = `❌ Lỗi: ${error.message}`;
}

});

// Chức năng tự động lấy dữ liệu let autoFetchInterval = null;

function startAutoFetch() { if (autoFetchInterval) { clearInterval(autoFetchInterval); }

// Kích hoạt lần lấy đầu tiên ngay lập tức
document.getElementById('fetchFromApi').click(); // Đặt khoảng thời gian cho mỗi 90 phút (90 * 60 * 1000 milliseconds)
autoFetchInterval = setInterval(() => { document.getElementById('fetchFromApi').click();
}, 90 * 60 * 1000);

}

function stopAutoFetch() { if (autoFetchInterval) { clearInterval(autoFetchInterval); autoFetchInterval = null; } }

// Thêm sự kiện cho checkbox tự động lấy dữ liệu document.addEventListener('DOMContentLoaded', function() { const autoFetchCheckbox = document.getElementById('autoFetch');

// Tải trạng thái đã lưu
chrome.storage.local.get(['autoFetchEnabled'], function(result) { if (result.autoFetchEnabled) { autoFetchCheckbox.checked = true; startAutoFetch(); }
}); autoFetchCheckbox.addEventListener('change', function() { if (this.checked) { startAutoFetch(); } else { stopAutoFetch(); } // Lưu trạng thái chrome.storage.local.set({ autoFetchEnabled: this.checked });
});

});

cảm ơn

Bình luận

Bài viết tương tự

- vừa được xem lúc

Upload file ajax với FormData

1. FormData là gì. FormData là một interface mới được HTML5 giới thiệu trong Web API. 2.

0 0 40

- vừa được xem lúc

Một số ví dụ sử dụng Ajax

1. Định nghĩa về ajax. AJAX viết tắt từ Asynchronous JavaScript and XML (JavaScript và XML không đồng bộ). .

0 0 64

- vừa được xem lúc

Tìm hiểu về FormData

1. Bài toán đặt ra. bạn sẽ thường phải chặn việc submit form và sử dụng ajax để xử lý dữ liệu form gửi lên. Bài toán 1:.

0 0 43

- vừa được xem lúc

Cùng xây dựng app Ajax đơn giản trong Rails

Chúng ta cùng thử viết một Web app đơn giản có sử dụng Ajax bằng Ruby on Rails nhé! . Trong bài viết mình sẽ nói thẳng vào cách xây dựng ajax luôn nên nếu các bạn chưa nắm được cách viết 1 app CRUD bằ

0 0 43

- vừa được xem lúc

Tính năng Giỏ Hàng trong Rails

Ở bài viết này mình sẽ tóm tắt cách dùng Ruby on Rails và Ajax để tạo 1 app có chức năng "thêm vào giỏ hàng" như các shop online. ! .

0 0 53

- vừa được xem lúc

Laravel Forgot Password : Gửi mail chứa OTP và sử dụng ajax

Hi, xin chào mọi người. Đây là cách làm của mình, mọi người có thể tham khảo nha.

0 0 11