210 lines
9.6 KiB
C++
210 lines
9.6 KiB
C++
#include "ServiceLogic.h"
|
|
#include "Core/Singleton.h"
|
|
#include "Database.h"
|
|
#include "HttpSession.h"
|
|
#include "Settings.h"
|
|
#include <sstream>
|
|
|
|
namespace ServiceLogic {
|
|
using namespace boost::beast;
|
|
|
|
boost::beast::http::response<boost::beast::http::string_body>
|
|
serverError(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view errorMessage) {
|
|
using namespace boost::beast;
|
|
http::response<http::string_body> res{http::status::internal_server_error, request.version()};
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(request.keep_alive());
|
|
std::ostringstream oss;
|
|
oss << "An error occurred: '" << errorMessage << "'";
|
|
res.body() = oss.str();
|
|
res.prepare_payload();
|
|
return res;
|
|
}
|
|
|
|
http::response<http::string_body> badRequest(const http::request<http::string_body> &request, std::string_view why) {
|
|
http::response<http::string_body> res{http::status::bad_request, request.version()};
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, "text/html");
|
|
res.keep_alive(request.keep_alive());
|
|
res.body() = std::string(why);
|
|
res.prepare_payload();
|
|
return res;
|
|
}
|
|
|
|
void visitAnalysis() {
|
|
using namespace Core;
|
|
static std::vector<std::string> urlFilter = {
|
|
"/", "/search", "/login", "/MessageBoard", "/我的笔记", "/我的笔记/", "/我的博客",
|
|
};
|
|
|
|
auto application = Singleton<Older::Application>::instance();
|
|
application->insertUrl("/api/v1/visit_analysis", [](Older::HttpSession &session, const Older::Application::Request &request,
|
|
const boost::urls::matches &matches) {
|
|
using namespace std::chrono;
|
|
using namespace boost::beast;
|
|
using namespace Core;
|
|
auto rootJson = boost::json::parse(request.body());
|
|
auto &root = rootJson.as_object();
|
|
std::string url;
|
|
if (root.contains("url")) {
|
|
url = root["url"].as_string();
|
|
}
|
|
auto database = Singleton<Older::Database>::instance();
|
|
if (std::filesystem::exists("amass_blog" + url) && (url.find("/我的博客/page") != 0) && (url.find("/wt") != 0)) {
|
|
if (url.size() > 1 && url.back() == '/') {
|
|
url.pop_back();
|
|
}
|
|
|
|
if (root.contains("visitor_uuid") && root.contains("user_agent")) {
|
|
auto timestamp = duration_cast<seconds>(system_clock::now().time_since_epoch());
|
|
auto visitorUuid = std::string(root["visitor_uuid"].as_string());
|
|
auto userAgent = std::string(root["user_agent"].as_string());
|
|
database->upsertVisitRecord(url, visitorUuid, userAgent, timestamp.count());
|
|
}
|
|
}
|
|
auto urlStats = database->visitorStats(url);
|
|
auto siteStats = database->siteStats();
|
|
|
|
boost::json::object reply;
|
|
reply["page_view_count"] = urlStats.totalViews;
|
|
reply["unique_visitor_count"] = urlStats.visitorCount;
|
|
reply["site_page_view_count"] = siteStats.totalViews;
|
|
reply["site_unique_visitor_count"] = siteStats.totalVisitors;
|
|
|
|
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
|
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
|
s.keep_alive(request.keep_alive());
|
|
s.body() = boost::json::serialize(reply);
|
|
s.prepare_payload();
|
|
session.reply(std::move(s));
|
|
});
|
|
|
|
application->insertUrl("/api/v1/most_viewed_urls", [](Older::HttpSession &session, const Older::Application::Request &request,
|
|
const boost::urls::matches &matches) {
|
|
using namespace boost::beast;
|
|
int size = 5;
|
|
std::error_code error;
|
|
if (!request.body().empty()) {
|
|
auto rootJson = boost::json::parse(request.body(), error);
|
|
if (error) {
|
|
LOG(info) << "<" << request.body() << "> parse json error: " << error.message();
|
|
} else {
|
|
auto &root = rootJson.as_object();
|
|
if (root.contains("size")) {
|
|
size = root.at("size").as_int64();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto database = Singleton<Older::Database>::instance();
|
|
auto stats = database->mostViewedUrls(size + urlFilter.size());
|
|
boost::json::array reply;
|
|
int index = 0;
|
|
for (auto stat : stats) {
|
|
if (std::find(urlFilter.cbegin(), urlFilter.cend(), stat.url) != urlFilter.cend()) continue;
|
|
boost::json::object object;
|
|
object["url"] = stat.url;
|
|
object["count"] = stat.totalViews;
|
|
reply.push_back(std::move(object));
|
|
index++;
|
|
if (index >= size) break;
|
|
}
|
|
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
|
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
|
s.keep_alive(request.keep_alive());
|
|
s.body() = boost::json::serialize(reply);
|
|
s.prepare_payload();
|
|
session.reply(std::move(s));
|
|
});
|
|
|
|
application->insertUrl(
|
|
"/api/v1/latest_viewed_urls",
|
|
[](Older::HttpSession &session, const Older::Application::Request &request, const boost::urls::matches &matches) {
|
|
using namespace boost::beast;
|
|
using namespace std::chrono;
|
|
int size = 5;
|
|
std::error_code error;
|
|
if (!request.body().empty()) {
|
|
auto rootJson = boost::json::parse(request.body(), error);
|
|
if (error) {
|
|
LOG(info) << "<" << request.body() << "> parse json error: " << error.message();
|
|
} else {
|
|
auto &root = rootJson.as_object();
|
|
if (root.contains("size")) {
|
|
size = root.at("size").as_int64();
|
|
}
|
|
}
|
|
}
|
|
auto database = Singleton<Older::Database>::instance();
|
|
auto stats = database->latestViewedUrls(size + urlFilter.size());
|
|
|
|
boost::json::array reply;
|
|
int index = 0;
|
|
for (auto &stat : stats) {
|
|
if (std::find(urlFilter.cbegin(), urlFilter.cend(), stat.url) != urlFilter.cend()) continue;
|
|
boost::json::object object;
|
|
object["url"] = stat.url;
|
|
object["time"] = stat.lastViewTime;
|
|
reply.push_back(std::move(object));
|
|
index++;
|
|
if (index >= size) break;
|
|
}
|
|
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
|
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
|
s.keep_alive(request.keep_alive());
|
|
s.body() = boost::json::serialize(reply);
|
|
s.prepare_payload();
|
|
session.reply(std::move(s));
|
|
});
|
|
}
|
|
|
|
void live2dBackend() {
|
|
using namespace Core;
|
|
auto application = Singleton<Older::Application>::instance();
|
|
application->insertUrl("/api/v1/live2d/{path*}", [](Older::HttpSession &session, const Older::Application::Request &request,
|
|
const boost::urls::matches &matches) {
|
|
auto settings = Singleton<Older::Settings>::instance();
|
|
using namespace boost::beast;
|
|
boost::urls::url_view view(request.target());
|
|
auto target = view.path();
|
|
// LOG(info) << target;
|
|
if (target.find("..") != boost::beast::string_view::npos) {
|
|
session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
|
|
return;
|
|
}
|
|
std::string path = ResponseUtility::pathCat(settings->live2dModelsRoot(), matches["path"]);
|
|
if (target.back() == '/') path.append("index.html");
|
|
if (std::filesystem::is_directory(path)) path.append("/index.html");
|
|
boost::beast::error_code ec;
|
|
http::file_body::value_type body;
|
|
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
|
|
if (ec == boost::beast::errc::no_such_file_or_directory) {
|
|
std::ostringstream oss;
|
|
oss << "The resource '" << target << "' was not found.";
|
|
LOG(error) << oss.str();
|
|
session.errorReply(request, http::status::not_found, oss.str());
|
|
return;
|
|
} else if (ec) {
|
|
session.reply(ServiceLogic::serverError(request, ec.message()));
|
|
return;
|
|
}
|
|
auto const size = body.size();
|
|
http::response<http::file_body> res{std::piecewise_construct, std::make_tuple(std::move(body)),
|
|
std::make_tuple(http::status::ok, request.version())};
|
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
|
res.set(http::field::content_type, ResponseUtility::mimeType(path));
|
|
// res.set(http::field::access_control_allow_origin, "*");
|
|
res.set(http::field::cache_control, "max-age=2592000");
|
|
res.set(http::field::expires, "Fri, 22 Nov 2124 13:30:28 GMT");
|
|
res.content_length(size);
|
|
res.keep_alive(request.keep_alive());
|
|
session.reply(std::move(res));
|
|
});
|
|
}
|
|
|
|
} // namespace ServiceLogic
|