文件
csp/backend/src/services/solution_access_service.cc

204 行
6.7 KiB
C++

#include "csp/services/solution_access_service.h"
#include <sqlite3.h>
#include <chrono>
#include <ctime>
#include <stdexcept>
#include <string>
namespace csp::services {
namespace {
constexpr int kViewCost = 2;
int64_t NowSec() {
using namespace std::chrono;
return duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
void CheckSqlite(int rc, sqlite3* db, const char* what) {
if (rc == SQLITE_OK || rc == SQLITE_ROW || rc == SQLITE_DONE) return;
throw std::runtime_error(std::string(what) + ": " + sqlite3_errmsg(db));
}
std::string ColText(sqlite3_stmt* stmt, int col) {
const unsigned char* txt = sqlite3_column_text(stmt, col);
return txt ? reinterpret_cast<const char*>(txt) : std::string();
}
std::string BuildDayKeyChina(int64_t ts_sec) {
const std::time_t shifted = static_cast<std::time_t>(ts_sec + 8 * 3600);
std::tm tm {};
gmtime_r(&shifted, &tm);
char buf[16] = {0};
std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tm);
return std::string(buf);
}
int QueryRating(sqlite3* db, int64_t user_id) {
sqlite3_stmt* stmt = nullptr;
const char* sql = "SELECT rating FROM users WHERE id=?";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare query rating");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
if (sqlite3_step(stmt) != SQLITE_ROW) {
sqlite3_finalize(stmt);
throw std::runtime_error("user not found");
}
const int rating = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return rating;
}
int QueryDailyUsage(sqlite3* db, int64_t user_id, const std::string& day_key) {
sqlite3_stmt* stmt = nullptr;
const char* sql =
"SELECT COUNT(1) FROM problem_solution_view_logs WHERE user_id=? AND day_key=?";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare query daily usage");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_bind_text(stmt, 2, day_key.c_str(), -1, SQLITE_TRANSIENT), db,
"bind day_key");
CheckSqlite(sqlite3_step(stmt), db, "step query daily usage");
const int used = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return used;
}
void InsertViewLog(sqlite3* db,
int64_t user_id,
int64_t problem_id,
const std::string& day_key,
int64_t viewed_at,
bool charged,
int cost) {
sqlite3_stmt* stmt = nullptr;
const char* sql =
"INSERT INTO problem_solution_view_logs(user_id,problem_id,day_key,viewed_at,charged,cost,created_at) "
"VALUES(?,?,?,?,?,?,?)";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare insert solution view log");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
CheckSqlite(sqlite3_bind_text(stmt, 3, day_key.c_str(), -1, SQLITE_TRANSIENT), db,
"bind day_key");
CheckSqlite(sqlite3_bind_int64(stmt, 4, viewed_at), db, "bind viewed_at");
CheckSqlite(sqlite3_bind_int(stmt, 5, charged ? 1 : 0), db, "bind charged");
CheckSqlite(sqlite3_bind_int(stmt, 6, cost), db, "bind cost");
CheckSqlite(sqlite3_bind_int64(stmt, 7, viewed_at), db, "bind created_at");
CheckSqlite(sqlite3_step(stmt), db, "insert solution view log");
sqlite3_finalize(stmt);
}
void DeductRating(sqlite3* db, int64_t user_id, int cost) {
sqlite3_stmt* stmt = nullptr;
const char* sql = "UPDATE users SET rating=rating-? WHERE id=?";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare deduct rating");
CheckSqlite(sqlite3_bind_int(stmt, 1, cost), db, "bind cost");
CheckSqlite(sqlite3_bind_int64(stmt, 2, user_id), db, "bind user_id");
CheckSqlite(sqlite3_step(stmt), db, "deduct rating");
sqlite3_finalize(stmt);
}
} // namespace
SolutionViewChargeResult SolutionAccessService::ConsumeSolutionView(
int64_t user_id,
int64_t problem_id) {
if (user_id <= 0 || problem_id <= 0) {
throw std::runtime_error("invalid user_id/problem_id");
}
sqlite3* db = db_.raw();
const int64_t now = NowSec();
const std::string day_key = BuildDayKeyChina(now);
db_.Exec("BEGIN IMMEDIATE");
bool committed = false;
try {
SolutionViewChargeResult result;
result.day_key = day_key;
result.viewed_at = now;
const int used_before = QueryDailyUsage(db, user_id, day_key);
result.daily_used_count = used_before;
const int rating_before = QueryRating(db, user_id);
result.rating_before = rating_before;
if (used_before <= 0) {
result.daily_free = true;
result.charged = false;
result.cost = 0;
result.rating_after = rating_before;
} else {
if (rating_before < kViewCost) {
result.granted = false;
result.charged = false;
result.cost = kViewCost;
result.rating_after = rating_before;
result.deny_reason = "rating not enough";
db_.Exec("ROLLBACK");
return result;
}
result.daily_free = false;
result.charged = true;
result.cost = kViewCost;
result.rating_after = rating_before - kViewCost;
DeductRating(db, user_id, kViewCost);
}
InsertViewLog(db,
user_id,
problem_id,
day_key,
now,
result.charged,
result.cost);
result.daily_used_count = used_before + 1;
db_.Exec("COMMIT");
committed = true;
return result;
} catch (...) {
if (!committed) {
try {
db_.Exec("ROLLBACK");
} catch (...) {
}
}
throw;
}
}
SolutionViewStats SolutionAccessService::QueryUserProblemViewStats(
int64_t user_id,
int64_t problem_id) {
SolutionViewStats stats;
if (user_id <= 0 || problem_id <= 0) return stats;
sqlite3* db = db_.raw();
sqlite3_stmt* stmt = nullptr;
const char* sql =
"SELECT COUNT(1),COALESCE(SUM(cost),0),MAX(viewed_at) "
"FROM problem_solution_view_logs WHERE user_id=? AND problem_id=?";
CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db,
"prepare query solution view stats");
CheckSqlite(sqlite3_bind_int64(stmt, 1, user_id), db, "bind user_id");
CheckSqlite(sqlite3_bind_int64(stmt, 2, problem_id), db, "bind problem_id");
CheckSqlite(sqlite3_step(stmt), db, "step query solution view stats");
stats.total_views = sqlite3_column_int(stmt, 0);
stats.total_cost = sqlite3_column_int(stmt, 1);
if (sqlite3_column_type(stmt, 2) != SQLITE_NULL) {
stats.last_viewed_at = sqlite3_column_int64(stmt, 2);
}
stats.has_viewed = stats.total_views > 0;
sqlite3_finalize(stmt);
return stats;
}
} // namespace csp::services