#include "Database.h" #include "BoostLog.h" #include "Session.h" #include #include #include #include #include std::unique_ptr Database::session() { if (!m_sqlConnectionPool) return {}; return std::make_unique(*m_sqlConnectionPool); } bool Database::open(const std::string &path) { try { auto connection = std::make_unique(path); connection->setProperty("show-queries", "true"); connection->setDateTimeStorage(Wt::Dbo::SqlDateTimeType::DateTime, Wt::Dbo::backend::DateTimeStorage::PseudoISO8601AsText); m_sqlConnectionPool = std::make_unique(std::move(connection), 10); session()->createTables(); } catch (const Wt::Dbo::Exception &e) { LOG(error) << e.code() << ": " << e.what(); } bool ret = true; int result = sqlite3_open(path.c_str(), &m_sqlite3); if (result != SQLITE_OK) { ret = false; LOG(error) << "open database failed."; } initialize(); return ret; } void Database::updateVisitCount(const std::string &url, const std::string &visitorUuid, const std::string &userAgent, int64_t time) { sqlite3_stmt *stmt; auto strippedUrl = url; if (strippedUrl.size() > 1 && strippedUrl.back() == '/') { strippedUrl.pop_back(); } const char *query = "SELECT id, page_view_count FROM visit_analysis WHERE url = ? AND visitor_uuid = ?"; if (sqlite3_prepare_v2(m_sqlite3, query, -1, &stmt, 0) != SQLITE_OK) { LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3); } sqlite3_bind_text(stmt, 1, strippedUrl.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, visitorUuid.c_str(), -1, SQLITE_STATIC); int id = -1; int page_view_count = 0; if (sqlite3_step(stmt) == SQLITE_ROW) { id = sqlite3_column_int(stmt, 0); page_view_count = sqlite3_column_int(stmt, 1); } sqlite3_finalize(stmt); if (id != -1) { // 更新记录 const char *updateQuery = "UPDATE visit_analysis SET last_user_agent = ?, last_view_time = ?, page_view_count = ? WHERE id = ?"; if (sqlite3_prepare_v2(m_sqlite3, updateQuery, -1, &stmt, 0) != SQLITE_OK) { LOG(error) << "Failed to prepare update statement: " << sqlite3_errmsg(m_sqlite3); } sqlite3_bind_text(stmt, 1, userAgent.c_str(), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 2, time); sqlite3_bind_int(stmt, 3, page_view_count + 1); sqlite3_bind_int(stmt, 4, id); if (sqlite3_step(stmt) != SQLITE_DONE) { LOG(error) << "Failed to update record: " << sqlite3_errmsg(m_sqlite3); } sqlite3_finalize(stmt); } else { // 插入新记录 const char *insertQuery = "INSERT INTO visit_analysis (url, visitor_uuid, last_user_agent, last_view_time, " "page_view_count) VALUES (?, ?, ?, ?, 1)"; if (sqlite3_prepare_v2(m_sqlite3, insertQuery, -1, &stmt, 0) != SQLITE_OK) { LOG(error) << "Failed to prepare insert statement: " << sqlite3_errmsg(m_sqlite3); } sqlite3_bind_text(stmt, 1, strippedUrl.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, visitorUuid.c_str(), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 3, userAgent.c_str(), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 4, time); if (sqlite3_step(stmt) != SQLITE_DONE) { LOG(error) << "Failed to insert record: " << sqlite3_errmsg(m_sqlite3) << std::endl; } sqlite3_finalize(stmt); } } void Database::clearVisitRecord() { char *message = nullptr; constexpr auto sql = "DELETE FROM visit_analysis"; int rc = sqlite3_exec(m_sqlite3, sql, nullptr, nullptr, &message); if (rc != SQLITE_OK) { LOG(error) << "SQL error: " << message; sqlite3_free(message); } } VisitAnalysis Database::siteVisitAnalysisData() { VisitAnalysis ret; sqlite3_stmt *stmt = nullptr; const char *sql = "SELECT COUNT(DISTINCT visitor_uuid) as unique_visitors, SUM(page_view_count) as " "total_page_views FROM visit_analysis"; if (sqlite3_prepare_v2(m_sqlite3, sql, -1, &stmt, nullptr) != SQLITE_OK) { LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3); return ret; } if (sqlite3_step(stmt) == SQLITE_ROW) { ret.uniqueVisitorCount = sqlite3_column_int(stmt, 0); ret.pageViewCount = sqlite3_column_int(stmt, 1); } else { LOG(error) << "Failed to execute query: " << sqlite3_errmsg(m_sqlite3); } sqlite3_finalize(stmt); return ret; } static std::vector urlFilter = { "/", "/search", "/LoginPage", "/MessageBoard", "/我的笔记", "/我的博客", }; std::list Database::mostViewedUrls(int size) { const char *sql = "SELECT url, SUM(page_view_count) AS total_page_view_count " "FROM visit_analysis " "GROUP BY url " "ORDER BY total_page_view_count DESC " "LIMIT ?"; sqlite3_stmt *stmt = nullptr; std::list ret; auto rc = sqlite3_prepare_v2(m_sqlite3, sql, -1, &stmt, nullptr); if (rc != SQLITE_OK) { LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3); return ret; } sqlite3_bind_int(stmt, 1, size + urlFilter.size()); while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { VisitAnalysis pv; pv.url = reinterpret_cast(sqlite3_column_text(stmt, 0)); if (std::find(urlFilter.cbegin(), urlFilter.cend(), pv.url) != urlFilter.cend()) continue; pv.pageViewCount = sqlite3_column_int(stmt, 1); if (ret.size() < size) { ret.push_back(pv); } } if (rc != SQLITE_DONE) { LOG(error) << "Failed to execute statement: " << sqlite3_errmsg(m_sqlite3); } sqlite3_finalize(stmt); return ret; } std::list Database::latestViewedUrls(int size) { std::list ret; sqlite3_stmt *stmt; const char *sql = R"( SELECT url, MAX(last_view_time) as last_view_time FROM visit_analysis GROUP BY url ORDER BY last_view_time DESC LIMIT ? )"; if (sqlite3_prepare_v2(m_sqlite3, sql, -1, &stmt, nullptr) != SQLITE_OK) { LOG(error) << "Failed to prepare statement: " << sqlite3_errmsg(m_sqlite3); return ret; } if (sqlite3_bind_int(stmt, 1, size + urlFilter.size()) != SQLITE_OK) { LOG(error) << "Failed to bind parameter: " << sqlite3_errmsg(m_sqlite3); sqlite3_finalize(stmt); return ret; } while (sqlite3_step(stmt) == SQLITE_ROW) { VisitAnalysis visit; visit.url = reinterpret_cast(sqlite3_column_text(stmt, 0)); if (std::find(urlFilter.cbegin(), urlFilter.cend(), visit.url) != urlFilter.cend()) continue; visit.lastViewTime = sqlite3_column_int64(stmt, 1); if (ret.size() < size) { ret.push_back(visit); } } return ret; } VisitAnalysis Database::visitAnalysisData(const std::string &url) { VisitAnalysis ret; sqlite3_stmt *stmt; std::string query = "SELECT SUM(page_view_count), COUNT(DISTINCT visitor_uuid) FROM visit_analysis WHERE url = '" + url + "';"; if (sqlite3_prepare_v2(m_sqlite3, query.c_str(), -1, &stmt, nullptr) == SQLITE_OK) { if (sqlite3_step(stmt) == SQLITE_ROW) { ret.pageViewCount = sqlite3_column_int(stmt, 0); ret.uniqueVisitorCount = sqlite3_column_int(stmt, 1); } sqlite3_finalize(stmt); } else { LOG(error) << "Failed to execute query: " << sqlite3_errmsg(m_sqlite3); } return ret; } void Database::initialize() { char *message = nullptr; auto sql = R"( CREATE TABLE IF NOT EXISTS visit_analysis ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, visitor_uuid TEXT NOT NULL, last_user_agent TEXT NOT NULL, last_view_time INTEGER NOT NULL, page_view_count INTEGER NOT NULL ); )"; int result = sqlite3_exec(m_sqlite3, sql, 0, 0, &message); if (result != SQLITE_OK) { LOG(error) << "Failed to create table: " << message << std::endl; sqlite3_free(message); } } Database::~Database() { if (m_sqlite3 != nullptr) { sqlite3_close(m_sqlite3); m_sqlite3 = nullptr; } }