实现live2d接口。
This commit is contained in:
parent
b5f1c3343a
commit
e9c3cde9de
17
.vscode/c_cpp_properties.json
vendored
Normal file
17
.vscode/c_cpp_properties.json
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"/opt/Libraries/boost_1_87_0/include"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "gnu++17",
|
||||
"intelliSenseMode": "linux-gcc-x64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
@ -1,22 +1,100 @@
|
||||
#include "Application.h"
|
||||
#include "Base/Messages.h"
|
||||
#include "Core/IoContext.h"
|
||||
#include "Core/MessageManager.h"
|
||||
#include "Core/Singleton.h"
|
||||
#include "HttpSession.h"
|
||||
#include "Router/router.hpp"
|
||||
#include "ServiceLogic.h"
|
||||
#include "Settings.h"
|
||||
#include "WeChat/Corporation/Context.h"
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
namespace Older {
|
||||
Application::Application() {
|
||||
|
||||
class ApplicationPrivate {
|
||||
public:
|
||||
std::shared_ptr<boost::urls::router<Application::RequestHandler>> router;
|
||||
std::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor;
|
||||
};
|
||||
|
||||
Application::Application() : m_d{new ApplicationPrivate()} {
|
||||
using namespace boost::urls;
|
||||
using namespace Core;
|
||||
m_d->router = std::make_shared<router<RequestHandler>>();
|
||||
|
||||
m_messageManager = Singleton<MessageManager>::construct();
|
||||
m_settings = Singleton<Settings>::construct();
|
||||
|
||||
m_ioContext = Singleton<IoContext>::construct(m_settings->threads());
|
||||
|
||||
m_corporationContext = Singleton<WeChat::Corporation::Context>::construct(*m_ioContext->ioContext());
|
||||
m_corporationContext->start();
|
||||
|
||||
m_d->router->insert("/api/v1/notify", [this](HttpSession &session, const Request &request, const matches &matches) {
|
||||
auto manager = Singleton<MessageManager>::instance();
|
||||
if (manager) {
|
||||
manager->publish<NotifyServerChan>(request);
|
||||
}
|
||||
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
|
||||
});
|
||||
}
|
||||
|
||||
boost::asio::io_context &Application::ioContext() {
|
||||
return *m_ioContext->ioContext();
|
||||
}
|
||||
|
||||
void Application::startAcceptHttpConnections(const std::string &address, uint16_t port) {
|
||||
m_d->acceptor = std::make_shared<boost::asio::ip::tcp::acceptor>(*m_ioContext->ioContext());
|
||||
boost::beast::error_code error;
|
||||
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::make_address(address), port);
|
||||
m_d->acceptor->open(endpoint.protocol(), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
m_d->acceptor->set_option(boost::asio::socket_base::reuse_address(true), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
m_d->acceptor->bind(endpoint, error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
m_d->acceptor->listen(boost::asio::socket_base::max_listen_connections, error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
asyncAcceptHttpConnections();
|
||||
}
|
||||
|
||||
void Application::insertUrl(std::string_view url, RequestHandler &&handler) {
|
||||
m_d->router->insert(url, std::move(handler));
|
||||
}
|
||||
|
||||
int Application::exec() {
|
||||
using namespace Core;
|
||||
auto settings = Singleton<Settings>::instance();
|
||||
ServiceLogic::live2dBackend();
|
||||
startAcceptHttpConnections(settings->server(), settings->port());
|
||||
m_ioContext->run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Application::asyncAcceptHttpConnections() {
|
||||
auto socket = std::make_shared<boost::asio::ip::tcp::socket>(boost::asio::make_strand(*m_ioContext->ioContext()));
|
||||
m_d->acceptor->async_accept(*socket, [self{shared_from_this()}, socket](const boost::system::error_code &error) {
|
||||
if (error) {
|
||||
if (error == boost::asio::error::operation_aborted) return;
|
||||
LOG(error) << error.message();
|
||||
} else {
|
||||
auto session = std::make_shared<HttpSession>(std::move(*socket), self->m_d->router);
|
||||
session->run();
|
||||
}
|
||||
self->asyncAcceptHttpConnections();
|
||||
});
|
||||
}
|
||||
} // namespace Older
|
||||
|
@ -1,26 +1,48 @@
|
||||
#ifndef __APPLICATION_H__
|
||||
#define __APPLICATION_H__
|
||||
|
||||
#include <memory>
|
||||
#include "Router/matches.hpp"
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace Core {
|
||||
class IoContext;
|
||||
class MessageManager;
|
||||
} // namespace Core
|
||||
|
||||
namespace WeChat {
|
||||
namespace Corporation {
|
||||
class Context;
|
||||
}
|
||||
} // namespace WeChat
|
||||
|
||||
namespace Older {
|
||||
|
||||
class Settings;
|
||||
class ApplicationPrivate;
|
||||
class HttpSession;
|
||||
|
||||
class Application {
|
||||
class Application : public std::enable_shared_from_this<Application> {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<Application>;
|
||||
using Request = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
using RequestHandler = std::function<void(HttpSession &, const Request &, const boost::urls::matches &)>;
|
||||
Application();
|
||||
boost::asio::io_context &ioContext();
|
||||
void startAcceptHttpConnections(const std::string &address, uint16_t port);
|
||||
void insertUrl(std::string_view url, RequestHandler &&handler);
|
||||
int exec();
|
||||
|
||||
protected:
|
||||
void asyncAcceptHttpConnections();
|
||||
|
||||
private:
|
||||
ApplicationPrivate *m_d = nullptr;
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
std::shared_ptr<Core::IoContext> m_ioContext;
|
||||
std::shared_ptr<Core::MessageManager> m_messageManager;
|
||||
std::shared_ptr<WeChat::Corporation::Context> m_corporationContext;
|
||||
};
|
||||
} // namespace Older
|
||||
#endif // __APPLICATION_H__
|
11
Base/Messages.h
Normal file
11
Base/Messages.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef __MESSAGES_H__
|
||||
#define __MESSAGES_H__
|
||||
|
||||
#include "Core/MessageManager.h"
|
||||
#include <boost/callable_traits/return_type.hpp>
|
||||
|
||||
struct NotifyServerChan {
|
||||
using Signature = void(const boost::beast::http::request<boost::beast::http::string_body> &);
|
||||
};
|
||||
|
||||
#endif // __MESSAGES_H__
|
@ -1,11 +1,24 @@
|
||||
find_package(Boost REQUIRED COMPONENTS json)
|
||||
|
||||
add_subdirectory(/root/Projects/Kylin Kylin)
|
||||
|
||||
add_executable(Older main.cpp
|
||||
WeChat/Corporation/Context.h WeChat/Corporation/Context.cpp
|
||||
|
||||
Application.h Application.cpp
|
||||
HttpSession.h HttpSession.cpp
|
||||
ResponseUtility.h ResponseUtility.cpp
|
||||
ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp
|
||||
Settings.h Settings.cpp
|
||||
)
|
||||
|
||||
target_include_directories(Older
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(Older
|
||||
PRIVATE Kylin::Core
|
||||
PRIVATE Kylin::Http
|
||||
PRIVATE Kylin::Router
|
||||
PRIVATE Boost::json
|
||||
)
|
119
HttpSession.cpp
Normal file
119
HttpSession.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
#include "HttpSession.h"
|
||||
#include "Core/Logger.h"
|
||||
#include <boost/beast/http/read.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/stacktrace.hpp>
|
||||
#include <boost/url/parse_path.hpp>
|
||||
#include <boost/url/url_view.hpp>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
namespace Older {
|
||||
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket,
|
||||
const std::shared_ptr<boost::urls::router<RequestHandler>> &router)
|
||||
: m_stream(std::move(socket)), m_router(router) {
|
||||
}
|
||||
|
||||
void HttpSession::run() {
|
||||
doRead();
|
||||
}
|
||||
|
||||
boost::beast::tcp_stream::executor_type HttpSession::executor() {
|
||||
return m_stream.get_executor();
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket HttpSession::releaseSocket() {
|
||||
return m_stream.release_socket();
|
||||
}
|
||||
|
||||
void HttpSession::errorReply(const Request &request, boost::beast::http::status status, boost::beast::string_view message) {
|
||||
using namespace boost::beast;
|
||||
// invalid route
|
||||
http::response<http::string_body> res{status, 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() = message;
|
||||
res.prepare_payload();
|
||||
|
||||
reply(std::move(res));
|
||||
}
|
||||
|
||||
void HttpSession::doRead() {
|
||||
// Construct a new parser for each message
|
||||
m_parser.emplace();
|
||||
|
||||
// Apply a reasonable limit to the allowed size
|
||||
// of the body in bytes to prevent abuse.
|
||||
m_parser->body_limit(std::numeric_limits<std::uint64_t>::max());
|
||||
m_parser->header_limit(std::numeric_limits<std::uint32_t>::max());
|
||||
m_buffer.clear();
|
||||
|
||||
// Set the timeout.
|
||||
m_stream.expires_after(std::chrono::seconds(30));
|
||||
// clang-format off
|
||||
boost::beast::http::async_read(m_stream, m_buffer, *m_parser, [self{shared_from_this()}](const boost::system::error_code &ec, std::size_t bytes_transferred) {
|
||||
self->onRead(ec, bytes_transferred);
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void HttpSession::onRead(const boost::beast::error_code &error, std::size_t) {
|
||||
using namespace boost::beast;
|
||||
if (error) {
|
||||
if (error == http::error::end_of_stream) {
|
||||
boost::beast::error_code e;
|
||||
m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, e);
|
||||
} else if (error != boost::asio::error::operation_aborted) {
|
||||
LOG(info) << error << " : " << error.message();
|
||||
}
|
||||
return;
|
||||
} else if (m_router.expired()) {
|
||||
LOG(error) << "router is null.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto &request = m_parser->get();
|
||||
auto path = boost::urls::parse_path(request.target());
|
||||
if (!path) {
|
||||
LOG(error) << request.target() << "failed, error: " << path.error().message();
|
||||
errorReply(request, http::status::bad_request, "Illegal request-target");
|
||||
return;
|
||||
}
|
||||
auto router = m_router.lock();
|
||||
boost::urls::matches matches;
|
||||
auto handler = router->find(*path, matches);
|
||||
if (handler) {
|
||||
try {
|
||||
(*handler)(*this, request, matches);
|
||||
} catch (const std::exception &e) {
|
||||
boost::stacktrace::stacktrace trace = boost::stacktrace::stacktrace::from_current_exception();
|
||||
LOG(error) << e.what() << ", trace:\n" << trace;
|
||||
}
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss << "The resource '" << request.target() << "' was not found.";
|
||||
auto message = oss.str();
|
||||
errorReply(request, http::status::not_found, message);
|
||||
LOG(error) << message;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onWrite(boost::beast::error_code ec, std::size_t, bool close) {
|
||||
if (ec) {
|
||||
if (ec == boost::asio::error::operation_aborted) return;
|
||||
std::cerr << "write: " << ec.message() << "\n";
|
||||
}
|
||||
|
||||
if (close) {
|
||||
// This means we should close the connection, usually because
|
||||
// the response indicated the "Connection: close" semantic.
|
||||
m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read another request
|
||||
doRead();
|
||||
}
|
||||
} // namespace Older
|
51
HttpSession.h
Normal file
51
HttpSession.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef HTTPSESSION_H
|
||||
#define HTTPSESSION_H
|
||||
|
||||
#include "Router/router.hpp"
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/write.hpp>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Older {
|
||||
|
||||
/** Represents an established HTTP connection
|
||||
*/
|
||||
class HttpSession : public std::enable_shared_from_this<HttpSession> {
|
||||
void doRead();
|
||||
void onWrite(boost::beast::error_code ec, std::size_t, bool close);
|
||||
|
||||
public:
|
||||
using Request = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
using RequestHandler = std::function<void(HttpSession &, const Request &, const boost::urls::matches &)>;
|
||||
HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr<boost::urls::router<RequestHandler>> &router);
|
||||
template <typename Response>
|
||||
void reply(Response &&response) {
|
||||
using ResponseType = typename std::decay_t<decltype(response)>;
|
||||
auto sp = std::make_shared<ResponseType>(std::forward<decltype(response)>(response));
|
||||
boost::beast::http::async_write(m_stream, *sp,
|
||||
[self = shared_from_this(), sp](boost::beast::error_code ec, std::size_t bytes) {
|
||||
self->onWrite(ec, bytes, sp->need_eof());
|
||||
});
|
||||
}
|
||||
void errorReply(const Request &request, boost::beast::http::status status, boost::beast::string_view message);
|
||||
boost::beast::tcp_stream::executor_type executor();
|
||||
boost::asio::ip::tcp::socket releaseSocket();
|
||||
void run();
|
||||
|
||||
protected:
|
||||
void onRead(const boost::beast::error_code &error, std::size_t);
|
||||
|
||||
private:
|
||||
boost::beast::tcp_stream m_stream;
|
||||
std::weak_ptr<boost::urls::router<RequestHandler>> m_router;
|
||||
boost::beast::flat_buffer m_buffer{std::numeric_limits<std::uint32_t>::max()};
|
||||
std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser;
|
||||
};
|
||||
} // namespace Older
|
||||
|
||||
#endif // HTTPSESSION_H
|
51
ResponseUtility.cpp
Normal file
51
ResponseUtility.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "ResponseUtility.h"
|
||||
#include "boost/beast.hpp"
|
||||
|
||||
namespace ResponseUtility {
|
||||
|
||||
std::string_view mimeType(std::string_view path) {
|
||||
using boost::beast::iequals;
|
||||
auto const ext = [&path] {
|
||||
auto const pos = path.rfind(".");
|
||||
if (pos == std::string_view::npos) return std::string_view{};
|
||||
return path.substr(pos);
|
||||
}();
|
||||
if (iequals(ext, ".pdf")) return "Application/pdf";
|
||||
if (iequals(ext, ".htm")) return "text/html";
|
||||
if (iequals(ext, ".html")) return "text/html";
|
||||
if (iequals(ext, ".php")) return "text/html";
|
||||
if (iequals(ext, ".css")) return "text/css";
|
||||
if (iequals(ext, ".txt")) return "text/plain";
|
||||
if (iequals(ext, ".js")) return "application/javascript";
|
||||
if (iequals(ext, ".json")) return "application/json";
|
||||
if (iequals(ext, ".xml")) return "application/xml";
|
||||
if (iequals(ext, ".swf")) return "application/x-shockwave-flash";
|
||||
if (iequals(ext, ".flv")) return "video/x-flv";
|
||||
if (iequals(ext, ".png")) return "image/png";
|
||||
if (iequals(ext, ".jpe")) return "image/jpeg";
|
||||
if (iequals(ext, ".jpeg")) return "image/jpeg";
|
||||
if (iequals(ext, ".jpg")) return "image/jpeg";
|
||||
if (iequals(ext, ".gif")) return "image/gif";
|
||||
if (iequals(ext, ".bmp")) return "image/bmp";
|
||||
if (iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
|
||||
if (iequals(ext, ".tiff")) return "image/tiff";
|
||||
if (iequals(ext, ".tif")) return "image/tiff";
|
||||
if (iequals(ext, ".svg")) return "image/svg+xml";
|
||||
if (iequals(ext, ".svgz")) return "image/svg+xml";
|
||||
return "application/text";
|
||||
}
|
||||
|
||||
std::string pathCat(std::string_view base, std::string_view path) {
|
||||
if (base.empty()) return std::string(path);
|
||||
std::string result(base);
|
||||
char constexpr path_separator = '/';
|
||||
if (result.back() == path_separator && path.front() == path_separator) {
|
||||
result.resize(result.size() - 1);
|
||||
} else if (result.back() != path_separator && path.front() != path_separator) {
|
||||
result.append("/");
|
||||
}
|
||||
result.append(path.data(), path.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace ResponseUtility
|
19
ResponseUtility.h
Normal file
19
ResponseUtility.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef RESPONSEUTILITY_H
|
||||
#define RESPONSEUTILITY_H
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace ResponseUtility {
|
||||
/**
|
||||
* @brief Return a reasonable mime type based on the extension of a file.
|
||||
*/
|
||||
std::string_view mimeType(std::string_view path);
|
||||
|
||||
/**
|
||||
* @brief Append an HTTP rel-path to a local filesystem path.The returned path is normalized for the
|
||||
* platform.
|
||||
*/
|
||||
std::string pathCat(std::string_view base, std::string_view path);
|
||||
} // namespace ResponseUtility
|
||||
|
||||
#endif // RESPONSEUTILITY_H
|
77
ServiceLogic.cpp
Normal file
77
ServiceLogic.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "ServiceLogic.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 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
|
53
ServiceLogic.h
Normal file
53
ServiceLogic.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef SERVICELOGIC_H
|
||||
#define SERVICELOGIC_H
|
||||
|
||||
#include "Application.h"
|
||||
#include "ResponseUtility.h"
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/vector_body.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <fstream>
|
||||
|
||||
using StringRequest = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
|
||||
|
||||
|
||||
namespace ServiceLogic {
|
||||
|
||||
template <class Send>
|
||||
static void onWechat(const Older::Application::Pointer &app, StringRequest &&request, Send &&send);
|
||||
|
||||
// Returns a server error response
|
||||
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);
|
||||
|
||||
boost::beast::http::response<boost::beast::http::string_body>
|
||||
badRequest(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view why);
|
||||
|
||||
template <class ResponseBody, class RequestBody>
|
||||
boost::beast::http::response<ResponseBody> make_200(const boost::beast::http::request<RequestBody> &request,
|
||||
typename ResponseBody::value_type body,
|
||||
boost::beast::string_view content) {
|
||||
boost::beast::http::response<ResponseBody> response{boost::beast::http::status::ok, request.version()};
|
||||
response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
response.set(boost::beast::http::field::content_type, content);
|
||||
response.body() = body;
|
||||
response.prepare_payload();
|
||||
response.keep_alive(request.keep_alive());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void live2dBackend();
|
||||
|
||||
|
||||
|
||||
|
||||
}; // namespace ServiceLogic
|
||||
|
||||
#include "ServiceLogic.inl"
|
||||
|
||||
#endif // SERVICELOGIC_H
|
41
ServiceLogic.inl
Normal file
41
ServiceLogic.inl
Normal file
@ -0,0 +1,41 @@
|
||||
#ifndef SERVICELOGIC_INL
|
||||
#define SERVICELOGIC_INL
|
||||
|
||||
#include "Core/Logger.h"
|
||||
#include "WeChat/OfficialAccount/Context.h"
|
||||
#include <boost/beast/http/empty_body.hpp>
|
||||
#include <boost/beast/http/file_body.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/url.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace ServiceLogic {
|
||||
|
||||
template <class Send>
|
||||
static void onWechat(const Older::Application::Pointer &app, const StringRequest &request, Send &&send) {
|
||||
using namespace boost::beast;
|
||||
boost::urls::url url(request.target());
|
||||
auto context = Core::Singleton<WeChatContext>::instance();
|
||||
http::response<boost::beast::http::string_body> response;
|
||||
if (request.count("Content-Type") > 0 && request.at("Content-Type") == "text/xml") {
|
||||
response.body() = context->reply(request.body());
|
||||
} else {
|
||||
auto query = url.params();
|
||||
if (auto iterator = query.find("echostr"); iterator != query.end()) {
|
||||
response.body() = (*iterator)->value;
|
||||
}
|
||||
}
|
||||
boost::beast::error_code ec;
|
||||
|
||||
response.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||
response.set(http::field::content_type, "text/xml;charset=UTF-8");
|
||||
response.keep_alive(request.keep_alive());
|
||||
|
||||
response.prepare_payload();
|
||||
return send(std::move(response));
|
||||
}
|
||||
} // namespace ServiceLogic
|
||||
|
||||
#endif // SERVICELOGIC_INL
|
13
Settings.cpp
13
Settings.cpp
@ -4,4 +4,17 @@ namespace Older {
|
||||
uint32_t Settings::threads() const {
|
||||
return m_threads;
|
||||
}
|
||||
|
||||
std::string Settings::server() const {
|
||||
return m_server;
|
||||
}
|
||||
|
||||
uint16_t Settings::port() const {
|
||||
return m_port;
|
||||
}
|
||||
|
||||
std::string Settings::live2dModelsRoot() const {
|
||||
return m_live2dModelsRoot;
|
||||
}
|
||||
|
||||
} // namespace Older
|
@ -2,14 +2,22 @@
|
||||
#define __SETTINGS_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Older {
|
||||
class Settings {
|
||||
public:
|
||||
uint32_t threads() const;
|
||||
std::string server() const;
|
||||
uint16_t port() const;
|
||||
std::string live2dModelsRoot() const;
|
||||
|
||||
private:
|
||||
uint32_t m_threads = 1;
|
||||
std::string m_server = "127.0.0.1";
|
||||
uint16_t m_port = 8081;
|
||||
|
||||
std::string m_live2dModelsRoot = "resources/live2d";
|
||||
};
|
||||
} // namespace Older
|
||||
|
||||
|
128
WeChat/Corporation/Context.cpp
Normal file
128
WeChat/Corporation/Context.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include "Context.h"
|
||||
#include "Base/Messages.h"
|
||||
#include "Core/Logger.h"
|
||||
#include "Core/MessageManager.h"
|
||||
#include "Http/Utility.h"
|
||||
#include <boost/asio/defer.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
|
||||
namespace WeChat {
|
||||
namespace Corporation {
|
||||
Context::Context(boost::asio::io_context &ioContext) : m_ioContext(ioContext), m_timer(ioContext) {
|
||||
using namespace Core;
|
||||
auto manager = Singleton<MessageManager>::instance();
|
||||
if (manager) {
|
||||
manager->subscribe<NotifyServerChan>(
|
||||
[this](const boost::beast::http::request<boost::beast::http::string_body> &request) { notify(request); });
|
||||
}
|
||||
}
|
||||
|
||||
void Context::sendMessage(MessageType type, const std::string &message) {
|
||||
boost::format target("/cgi-bin/message/send?access_token=%1%");
|
||||
target % m_accessToken;
|
||||
|
||||
boost::json::object msg;
|
||||
msg["content"] = message;
|
||||
|
||||
boost::json::object request;
|
||||
request["touser"] = "@all";
|
||||
request["agentid"] = agentid;
|
||||
if (type == MessageType::Markdown) {
|
||||
request["msgtype"] = "markdown";
|
||||
request["markdown"] = std::move(msg);
|
||||
} else {
|
||||
request["msgtype"] = "text";
|
||||
request["text"] = std::move(msg);
|
||||
}
|
||||
auto body = boost::json::serialize(request);
|
||||
|
||||
boost::beast::error_code error;
|
||||
auto response = Https::post(m_ioContext, host, port, target.str(), body, error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
LOG(info) << response;
|
||||
}
|
||||
|
||||
void Context::start() {
|
||||
boost::asio::defer(m_ioContext, [ptr{weak_from_this()}]() {
|
||||
if (ptr.expired()) {
|
||||
LOG(error) << "Context instance was expired";
|
||||
return;
|
||||
}
|
||||
auto self = ptr.lock();
|
||||
self->updateAccessToken();
|
||||
});
|
||||
}
|
||||
|
||||
void Context::notify(const RequestType &request) {
|
||||
boost::system::error_code error;
|
||||
auto json = boost::json::parse(request.body(), error);
|
||||
if (error) {
|
||||
LOG(error) << "parse: [" << request.body() << "] failed, reason: " << error.message();
|
||||
return;
|
||||
}
|
||||
// LOG(debug) << "parse: [" << request.body() << "] succeed.";
|
||||
auto &req = json.as_object();
|
||||
MessageType type = MessageType::Text;
|
||||
if (req.contains("type")) {
|
||||
if (req.at("type").as_string() == "markdown") {
|
||||
type = MessageType::Markdown;
|
||||
}
|
||||
}
|
||||
if (req.contains("msg")) {
|
||||
std::string msg(req.at("msg").as_string());
|
||||
sendMessage(type, std::move(msg));
|
||||
}
|
||||
}
|
||||
|
||||
void Context::updateAccessToken() {
|
||||
boost::beast::error_code error;
|
||||
|
||||
boost::format target("/cgi-bin/gettoken?corpid=%1%&corpsecret=%2%");
|
||||
target % corpid % corpsecret;
|
||||
|
||||
auto response = Https::get(m_ioContext, host, port, target.str(), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
if (response.empty()) {
|
||||
LOG(warning) << "response is empty.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto json = boost::json::parse(response);
|
||||
auto &accessTokenObject = json.as_object();
|
||||
int errcode = accessTokenObject.count("errcode") > 0 ? accessTokenObject.at("errcode").as_int64() : -1;
|
||||
if (errcode != 0) {
|
||||
LOG(error) << "get access_token failed,code: " << errcode << ", message: " << accessTokenObject.at("errmsg").as_string();
|
||||
return;
|
||||
}
|
||||
m_accessToken = accessTokenObject.at("access_token").as_string();
|
||||
auto expires_in = accessTokenObject.at("expires_in").as_int64();
|
||||
// LOG(info) << "access_token: " << m_accessToken;
|
||||
LOG(info) << "re-access_token after " << expires_in << " s.";
|
||||
m_timer.expires_after(std::chrono::seconds(expires_in));
|
||||
m_timer.async_wait([this](const boost::system::error_code &error) {
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
updateAccessToken();
|
||||
});
|
||||
|
||||
static bool started = true;
|
||||
if (started) {
|
||||
sendMessage(MessageType::Text, "您好,艾玛已上线......");
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Corporation
|
||||
} // namespace WeChat
|
49
WeChat/Corporation/Context.h
Normal file
49
WeChat/Corporation/Context.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef __CORPORATIONCONTEXT_H__
|
||||
#define __CORPORATIONCONTEXT_H__
|
||||
|
||||
#include "Core/Singleton.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
|
||||
namespace WeChat {
|
||||
namespace Corporation {
|
||||
class Context : public std::enable_shared_from_this<Context> {
|
||||
friend class Core::Singleton<Context>;
|
||||
|
||||
public:
|
||||
enum MessageType {
|
||||
Text,
|
||||
Markdown,
|
||||
};
|
||||
using RequestType = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
|
||||
void sendMessage(MessageType type, const std::string &message);
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param request
|
||||
* @example curl -H "Content-Type: application/json" -X POST -d '{"user_id": "123", "msg":"OK!" }'
|
||||
* https://amass.fun/notify
|
||||
*/
|
||||
void notify(const RequestType &request);
|
||||
|
||||
protected:
|
||||
Context(boost::asio::io_context &ioContext);
|
||||
void updateAccessToken();
|
||||
|
||||
private:
|
||||
boost::asio::io_context &m_ioContext;
|
||||
boost::asio::steady_timer m_timer;
|
||||
std::string m_accessToken;
|
||||
|
||||
constexpr static auto host = "qyapi.weixin.qq.com";
|
||||
constexpr static auto port = "443";
|
||||
constexpr static auto corpid = "ww1a786851749bdadc";
|
||||
constexpr static auto corpsecret = "LlyJmYLIBOxJkQxkhwyqNVf550AUQ3JT2MT4yuS31i0";
|
||||
constexpr static auto agentid = 1000002;
|
||||
};
|
||||
} // namespace Corporation
|
||||
} // namespace WeChat
|
||||
#endif // __CORPORATIONCONTEXT_H__
|
183
WeChat/OfficialAccount/Context.cpp
Normal file
183
WeChat/OfficialAccount/Context.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
#include "WeChatContext.h"
|
||||
#include "../ServiceManager.h"
|
||||
#include "BoostLog.h"
|
||||
#include "WeChatSession.h"
|
||||
#include <NetworkUtility.h>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/asio/defer.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <sstream>
|
||||
|
||||
std::string WeChatContext::reply(const std::string &body) {
|
||||
std::ostringstream oss;
|
||||
LOG(info) << "someone send message: \n" << body;
|
||||
boost::property_tree::ptree ptree;
|
||||
std::istringstream iss(body);
|
||||
boost::property_tree::read_xml(iss, ptree);
|
||||
|
||||
auto ToUserName = ptree.get_optional<std::string>("xml.ToUserName");
|
||||
if (!ToUserName) {
|
||||
LOG(error) << "request dont contain ToUserName.";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
auto FromUserName = ptree.get<std::string>("xml.FromUserName");
|
||||
auto CreateTime = ptree.get<std::string>("xml.CreateTime");
|
||||
auto MsgType = ptree.get<std::string>("xml.MsgType");
|
||||
auto content = ptree.get<std::string>("xml.Content");
|
||||
auto MsgId = ptree.get<std::string>("xml.MsgId");
|
||||
|
||||
std::shared_ptr<WeChatSession> session;
|
||||
if (m_sessions.count(FromUserName) > 0) {
|
||||
session = m_sessions.at(FromUserName);
|
||||
} else {
|
||||
session = std::make_shared<WeChatSession>(FromUserName);
|
||||
m_sessions.emplace(FromUserName, session);
|
||||
}
|
||||
boost::algorithm::trim(content);
|
||||
auto reply = session->processInput(content);
|
||||
|
||||
boost::property_tree::ptree sendXml;
|
||||
sendXml.put("xml.Content", reply);
|
||||
LOG(info) << "send " << FromUserName << ": " << reply;
|
||||
|
||||
sendXml.put("xml.ToUserName", FromUserName);
|
||||
sendXml.put("xml.FromUserName", *ToUserName);
|
||||
sendXml.put("xml.CreateTime", CreateTime);
|
||||
sendXml.put("xml.MsgType", MsgType);
|
||||
|
||||
boost::property_tree::write_xml(oss, sendXml);
|
||||
// LOG(info) << "reply content:\n " << oss.str();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
WeChatContext::WeChatContext(boost::asio::io_context &ioContext)
|
||||
: m_ioContext(ioContext), m_timer(ioContext), m_sessionsExpireTimer(ioContext) {
|
||||
boost::asio::defer(m_ioContext, [this]() { updateAccessToken(); });
|
||||
}
|
||||
|
||||
void WeChatContext::updateAccessToken() {
|
||||
boost::beast::error_code error;
|
||||
|
||||
boost::format target("/cgi-bin/token?grant_type=client_credential&appid=%1%&secret=%2%");
|
||||
target % appid % secret;
|
||||
|
||||
auto response = Https::get(m_ioContext, host, port, target.str(), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
if (response.empty()) {
|
||||
LOG(warning) << "response is empty.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto json = boost::json::parse(response);
|
||||
auto &accessTokenObject = json.as_object();
|
||||
if (accessTokenObject.count("errcode")) {
|
||||
LOG(error) << "get access_token failed,code: " << accessTokenObject.at("errcode").as_int64()
|
||||
<< ", message: " << accessTokenObject.at("errmsg").as_string();
|
||||
return;
|
||||
}
|
||||
m_accessToken = accessTokenObject.at("access_token").as_string();
|
||||
auto expires_in = accessTokenObject.at("expires_in").as_int64();
|
||||
// LOG(info) << "access_token: " << m_accessToken;
|
||||
LOG(info) << "re-access_token after " << expires_in << " s.";
|
||||
m_timer.expires_after(std::chrono::seconds(expires_in));
|
||||
m_timer.async_wait([this](const boost::system::error_code &error) {
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
updateAccessToken();
|
||||
});
|
||||
broadcast("hello,amass.");
|
||||
}
|
||||
|
||||
WeChatContext::OpenIds WeChatContext::users() {
|
||||
boost::beast::error_code error;
|
||||
|
||||
boost::format target("/cgi-bin/user/get?access_token=%1%");
|
||||
|
||||
target % m_accessToken;
|
||||
auto response = Https::get(m_ioContext, host, port, target.str(), error);
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto json = boost::json::parse(response);
|
||||
auto &responseObject = json.as_object();
|
||||
if (responseObject.contains("errcode")) {
|
||||
LOG(error) << responseObject.at("errmsg").as_string();
|
||||
return {};
|
||||
}
|
||||
auto &users = responseObject.at("data").as_object().at("openid").as_array();
|
||||
if (users.empty()) {
|
||||
LOG(info) << "now we have no users.";
|
||||
}
|
||||
OpenIds ret;
|
||||
for (auto &id : users) {
|
||||
ret.emplace_back(id.as_string());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string WeChatContext::broadcast(const std::string_view &message) {
|
||||
boost::json::object messageObject;
|
||||
auto users = this->users();
|
||||
LOG(info) << "users: " << users;
|
||||
if (users.size() < 2) users.emplace_back("fake_user");
|
||||
boost::json::array usersArray;
|
||||
for (auto &user : users) {
|
||||
usersArray.emplace_back(user);
|
||||
}
|
||||
messageObject.emplace("touser", std::move(usersArray));
|
||||
messageObject.emplace("msgtype", "text");
|
||||
|
||||
boost::json::object textObject;
|
||||
textObject.emplace("content", message.data());
|
||||
|
||||
messageObject.emplace("text", std::move(textObject));
|
||||
|
||||
boost::format target("/cgi-bin/message/mass/send?access_token=%1%");
|
||||
|
||||
target % m_accessToken;
|
||||
|
||||
boost::system::error_code error;
|
||||
|
||||
auto response = Https::post(m_ioContext, host, port, target.str(), boost::json::serialize(messageObject), error);
|
||||
if (error) {
|
||||
// LOG(error) << error.message();
|
||||
return response;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
void WeChatContext::cleanExpiredSessions(const boost::system::error_code &error) {
|
||||
if (error) {
|
||||
LOG(error) << error.message();
|
||||
return;
|
||||
}
|
||||
auto now = std::chrono::system_clock::now();
|
||||
for (auto iterator = m_sessions.begin(); iterator != m_sessions.cend();) {
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(now - iterator->second->lastAccessedTime()) >
|
||||
sessionExpireTime) {
|
||||
iterator = m_sessions.erase(iterator);
|
||||
} else {
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
m_sessionsExpireTimer.expires_after(sessionExpireTime);
|
||||
m_sessionsExpireTimer.async_wait([ptr{weak_from_this()}](const boost::system::error_code &error) {
|
||||
if (ptr.expired()) return;
|
||||
ptr.lock()->cleanExpiredSessions(error);
|
||||
});
|
||||
}
|
47
WeChat/OfficialAccount/Context.h
Normal file
47
WeChat/OfficialAccount/Context.h
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef WECHATCONTEXT_H
|
||||
#define WECHATCONTEXT_H
|
||||
|
||||
#include "Core/Singleton.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
class WeChatSession;
|
||||
|
||||
class WeChatContext : public std::enable_shared_from_this<WeChatContext> {
|
||||
public:
|
||||
using OpenIds = std::vector<std::string>;
|
||||
|
||||
/**
|
||||
* @brief onWechat()函数调用了此函数,对接收到的消息进行处理
|
||||
*
|
||||
* @param body
|
||||
* @return std::string 返回给微信服务器
|
||||
*/
|
||||
std::string reply(const std::string &body);
|
||||
|
||||
protected:
|
||||
WeChatContext(boost::asio::io_context &ioContext);
|
||||
|
||||
void updateAccessToken();
|
||||
OpenIds users();
|
||||
std::string broadcast(const std::string_view &message);
|
||||
void cleanExpiredSessions(const boost::system::error_code &error = boost::system::error_code());
|
||||
|
||||
private:
|
||||
boost::asio::io_context &m_ioContext;
|
||||
boost::asio::steady_timer m_timer;
|
||||
std::string m_accessToken;
|
||||
|
||||
boost::asio::steady_timer m_sessionsExpireTimer;
|
||||
std::unordered_map<std::string, std::shared_ptr<WeChatSession>> m_sessions;
|
||||
|
||||
constexpr static std::chrono::seconds sessionExpireTime{5};
|
||||
constexpr static int httpVersion = 11;
|
||||
constexpr static auto host = "api.weixin.qq.com";
|
||||
constexpr static auto port = "443";
|
||||
constexpr static auto appid = "wxdb4253b5c4259708";
|
||||
constexpr static auto secret = "199780c4d3205d8b7b1f9be3382fbf82";
|
||||
};
|
||||
|
||||
#endif // WECHATCONTEXT_H
|
101
WeChat/OfficialAccount/Session.cpp
Normal file
101
WeChat/OfficialAccount/Session.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
#include "WeChatSession.h"
|
||||
#include "../ServiceManager.h"
|
||||
#include <BoostLog.h>
|
||||
#include <DateTime.h>
|
||||
|
||||
WeChatSession::WeChatSession(const std::string_view &username) : m_username(username) {
|
||||
m_lastAccessedTime = std::chrono::system_clock::now();
|
||||
initiate();
|
||||
}
|
||||
|
||||
std::string WeChatSession::processInput(const std::string_view &text) {
|
||||
ProcessInputEvent e;
|
||||
e.text = text;
|
||||
process_event(e);
|
||||
m_lastAccessedTime = std::chrono::system_clock::now();
|
||||
|
||||
std::string ret = std::move(m_reply);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WeChatSession::printHelp() {
|
||||
std::ostringstream oss;
|
||||
oss << "1:设置闹钟" << std::endl;
|
||||
oss << "2:TTS" << std::endl;
|
||||
oss << "3:当前时间" << std::endl;
|
||||
oss << "4:随机播放音乐" << std::endl;
|
||||
oss << "5:停止播放音乐" << std::endl;
|
||||
oss << "<其它>:帮助" << std::endl;
|
||||
setReply(oss.str());
|
||||
}
|
||||
|
||||
void WeChatSession::printCurrentDateTime() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<CurrentDatetimeService>(CurrentDatetime);
|
||||
setReply("艾玛收到!将为您播报当前时间");
|
||||
}
|
||||
|
||||
void WeChatSession::playRandomMusic() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<PlayRandomMusicService>(PlayRandomMusic);
|
||||
setReply("艾玛收到!将为您随机播放音乐");
|
||||
}
|
||||
|
||||
void WeChatSession::stopPlayMusic() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage(StopPlayMusic);
|
||||
setReply("艾玛收到!正在为您停止播放音乐");
|
||||
}
|
||||
|
||||
std::chrono::system_clock::time_point WeChatSession::lastAccessedTime() const {
|
||||
return m_lastAccessedTime;
|
||||
}
|
||||
|
||||
void WeChatSession::setReply(std::string &&reply) {
|
||||
m_reply = std::move(reply);
|
||||
}
|
||||
|
||||
boost::statechart::result IdleState::react(const ProcessInputEvent &e) {
|
||||
auto &text = e.text;
|
||||
if (text == "1") {
|
||||
outermost_context().setReply("请输入闹钟时间:");
|
||||
return transit<SetAlarmState>();
|
||||
} else if (text == "2") {
|
||||
outermost_context().setReply("请输入TTS文字:");
|
||||
return transit<SetTtsState>();
|
||||
} else if (text == "3") {
|
||||
outermost_context().printCurrentDateTime();
|
||||
return discard_event();
|
||||
} else if (text == "4") {
|
||||
outermost_context().playRandomMusic();
|
||||
return discard_event();
|
||||
} else if (text == "5") {
|
||||
outermost_context().stopPlayMusic();
|
||||
return discard_event();
|
||||
} else {
|
||||
outermost_context().stopPlayMusic();
|
||||
outermost_context().printHelp();
|
||||
return discard_event();
|
||||
}
|
||||
}
|
||||
|
||||
boost::statechart::result SetAlarmState::react(const ProcessInputEvent &e) {
|
||||
auto &text = e.text;
|
||||
auto [hour, minute, second] = DateTime::parseTime(text);
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<SetAlarmClockService>(SetAlarmClock, hour, minute);
|
||||
std::ostringstream oss;
|
||||
oss << "set alarm clock at " << (int)hour << ":" << (int)minute;
|
||||
this->outermost_context().setReply(oss.str());
|
||||
return transit<IdleState>();
|
||||
}
|
||||
|
||||
SetAlarmState::SetAlarmState() {
|
||||
}
|
||||
|
||||
boost::statechart::result SetTtsState::react(const ProcessInputEvent &e) {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage(TextToSpeech, e.text);
|
||||
outermost_context().setReply(e.text.data());
|
||||
return transit<IdleState>();
|
||||
}
|
53
WeChat/OfficialAccount/Session.h
Normal file
53
WeChat/OfficialAccount/Session.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef __WECHATSESSION_H__
|
||||
#define __WECHATSESSION_H__
|
||||
|
||||
#include <boost/statechart/custom_reaction.hpp>
|
||||
#include <boost/statechart/simple_state.hpp>
|
||||
#include <boost/statechart/state_machine.hpp>
|
||||
#include <chrono>
|
||||
#include <string_view>
|
||||
|
||||
class ProcessInputEvent : public boost::statechart::event<ProcessInputEvent> {
|
||||
public:
|
||||
std::string text;
|
||||
};
|
||||
|
||||
class IdleState;
|
||||
|
||||
class WeChatSession : public boost::statechart::state_machine<WeChatSession, IdleState> {
|
||||
public:
|
||||
WeChatSession(const std::string_view &username);
|
||||
std::string processInput(const std::string_view &text);
|
||||
void printHelp();
|
||||
void printCurrentDateTime();
|
||||
void playRandomMusic();
|
||||
void stopPlayMusic();
|
||||
std::chrono::system_clock::time_point lastAccessedTime() const;
|
||||
void setReply(std::string &&reply);
|
||||
|
||||
private:
|
||||
std::string m_username;
|
||||
std::chrono::system_clock::time_point m_lastAccessedTime;
|
||||
std::string m_reply;
|
||||
};
|
||||
|
||||
class IdleState : public boost::statechart::simple_state<IdleState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
};
|
||||
|
||||
class SetAlarmState : public boost::statechart::simple_state<SetAlarmState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
SetAlarmState();
|
||||
};
|
||||
|
||||
class SetTtsState : public boost::statechart::simple_state<SetTtsState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
};
|
||||
|
||||
#endif // __WECHATSESSION_H__
|
14
resources/older.service
Normal file
14
resources/older.service
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Http Server
|
||||
After=network.target
|
||||
# /etc/systemd/system/older.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/root/Server/Older
|
||||
WorkingDirectory=/root/Server
|
||||
Restart=on-failure
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
Loading…
x
Reference in New Issue
Block a user