Mirgate HttpServer finished.

This commit is contained in:
luocai 2023-07-21 16:17:01 +08:00
parent 8919d993ab
commit 15024841d0
45 changed files with 2993 additions and 2 deletions

1
.gitignore vendored
View File

@ -40,3 +40,4 @@ target_wrapper.*
# QtCreator CMake # QtCreator CMake
CMakeLists.txt.user* CMakeLists.txt.user*
build

View File

@ -1,3 +1,11 @@
cmake_minimum_required(VERSION 3.17) cmake_minimum_required(VERSION 3.17)
project(Older) project(Older)
include(FetchContent)
FetchContent_Declare(Kylin
GIT_REPOSITORY https://gitea.amass.fun/amass/Kylin.git
)
FetchContent_MakeAvailable(Kylin)
add_subdirectory(Server)

View File

@ -0,0 +1,92 @@
#include "AlarmClockServer.h"
#include "ServiceManager.h"
#include <boost/json/object.hpp>
#include <boost/json/serialize.hpp>
AlarmClockServer::AlarmClockServer(boost::asio::io_context &context)
: m_publisher(context, ZeroMQ::SocketType::Pub), m_timer(context) {
auto manager = Amass::Singleton<ServiceManager>::instance();
manager->registerTopic(SetAlarmClock, [this](uint8_t hour, uint8_t minute) { setAlarmTime(hour, minute); });
manager->registerTopic(TextToSpeech, [this](const std::string &text) { textToSpeech(text); });
manager->registerTopic(CurrentDatetime, [this]() { currentDatatime(); });
manager->registerTopic(PlayRandomMusic, [this]() { playRandomMusic(); });
manager->registerTopic(StopPlayMusic, [this]() { stopPlayMusic(); });
}
void AlarmClockServer::startPublishHeartBeat() {
m_timer.expires_after(std::chrono::seconds(25));
m_timer.async_wait([this](const boost::system::error_code &error) {
if (error) {
LOG(error) << error.message();
return;
}
boost::json::object obj;
obj["type"] = HeartBeat;
ZeroMQ::Message message(boost::json::serialize(obj));
auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait);
startPublishHeartBeat();
});
}
void AlarmClockServer::listen(const std::string_view &host, const std::string_view &port) {
std::ostringstream oss;
oss << "tcp://" << host << ":" << port;
boost::system::error_code error;
auto address = oss.str();
m_publisher.bind(address, error);
if (error) {
LOG(error) << error.message();
return;
}
startPublishHeartBeat();
LOG(info) << "AlarmClockServer bind address: " << address;
}
void AlarmClockServer::setAlarmTime(uint8_t hour, uint8_t minute) {
boost::json::object obj;
obj["type"] = SetAlarmClock;
obj["hour"] = hour;
obj["minute"] = minute;
auto body = boost::json::serialize(obj);
ZeroMQ::Message message(body);
auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait);
LOG(info) << "send to client: " << body << ", size: " << size;
}
void AlarmClockServer::textToSpeech(const std::string_view &text) {
boost::json::object obj;
obj["type"] = TextToSpeech.topic;
obj["text"] = text.data();
auto body = boost::json::serialize(obj);
ZeroMQ::Message message(body);
auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait);
LOG(info) << "text:[" << text << "].\nsend to client: " << body << ", size: " << size;
}
void AlarmClockServer::currentDatatime() {
boost::json::object obj;
obj["type"] = CurrentDatetime;
auto body = boost::json::serialize(obj);
ZeroMQ::Message message(body);
auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait);
LOG(info) << "send to client: " << body << ", size: " << size;
}
void AlarmClockServer::playRandomMusic() {
boost::json::object obj;
obj["type"] = PlayRandomMusic;
auto body = boost::json::serialize(obj);
ZeroMQ::Message message(body);
auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait);
LOG(info) << "send to client: " << body << ", size: " << size;
}
void AlarmClockServer::stopPlayMusic() {
boost::json::object obj;
obj["type"] = StopPlayMusic.topic;
auto body = boost::json::serialize(obj);
ZeroMQ::Message message(body);
auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait);
LOG(info) << "send to client: " << body << ", size: " << size;
}

28
Server/AlarmClockServer.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef __ALARMCLOCKSERVER_H__
#define __ALARMCLOCKSERVER_H__
#include "ZeroMQSocket.h"
#include <Singleton.h>
#include <boost/asio/steady_timer.hpp>
class AlarmClockServer {
friend class Amass::Singleton<AlarmClockServer>;
public:
void listen(const std::string_view &host, const std::string_view &port);
void setAlarmTime(uint8_t hour, uint8_t minute);
void textToSpeech(const std::string_view &text);
void currentDatatime();
protected:
AlarmClockServer(boost::asio::io_context &context);
void startPublishHeartBeat();
void playRandomMusic();
void stopPlayMusic();
private:
ZeroMQ::Socket m_publisher;
boost::asio::steady_timer m_timer;
};
#endif // __ALARMCLOCKSERVER_H__

27
Server/CMakeLists.txt Normal file
View File

@ -0,0 +1,27 @@
find_package(Boost COMPONENTS program_options json REQUIRED)
add_executable(Server main.cpp
AlarmClockServer.h AlarmClockServer.cpp
HttpSession.h HttpSession.cpp
Listener.h Listener.cpp
ResponseUtility.h ResponseUtility.cpp
ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp
ServiceManager.h
SharedState.h SharedState.cpp
UdpServer.h UdpServer.cpp
WebsocketSession.h WebsocketSession.cpp
WeChatContext/CorporationContext.h WeChatContext/CorporationContext.cpp
WeChatContext/WeChatContext.h WeChatContext/WeChatContext.cpp
WeChatContext/WeChatSession.h WeChatContext/WeChatSession.cpp
)
target_link_libraries(Server
PRIVATE Universal
PRIVATE HttpProxy
PRIVATE AsioZeroMQ
PRIVATE ${Boost_LIBRARIES}
)
set_target_properties(Server PROPERTIES
OUTPUT_NAME HttpServer
)

100
Server/HttpSession.cpp Normal file
View File

@ -0,0 +1,100 @@
#include "HttpSession.h"
#include "WebsocketSession.h"
#include <boost/config.hpp>
#include <boost/url/parse_path.hpp>
#include <boost/url/url_view.hpp>
#include <iostream>
#include <limits>
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr<SharedState> &state)
: m_stream(std::move(socket)), m_state(state) {
// m_buffer.reserve(1000 * 1000 * 1000);
}
void HttpSession::run() {
doRead();
}
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));
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);
});
}
void HttpSession::onRead(boost::beast::error_code ec, std::size_t) {
// This means they closed the connection
if (ec == boost::beast::http::error::end_of_stream) {
m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
return;
}
if (ec) {
if (ec == boost::asio::error::operation_aborted) return;
LOG(info) << ec << " : " << ec.message();
return;
}
auto &request = m_parser->get();
// See if it is a WebSocket Upgrade
if (boost::beast::websocket::is_upgrade(request)) {
// Create a websocket session, transferring ownership
// of both the socket and the HTTP request.
auto session = std::make_shared<WebSocketSession>(m_stream.release_socket(), m_state);
session->run(m_parser->release());
return;
}
boost::urls::url_view view(request.target());
auto path = boost::urls::parse_path(view.path());
TemplateMatches matches;
auto handler = m_state->find(*path, matches);
if (handler) {
(*handler)(*this, request, matches);
} else {
auto message = "The resource '" + std::string(path->buffer()) + "' was not found.";
errorReply(request, boost::beast::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();
}

42
Server/HttpSession.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef HTTPSESSION_H
#define HTTPSESSION_H
#include "SharedState.h"
#include "boost/beast.hpp"
#include <cstdlib>
#include <memory>
#include <optional>
/** Represents an established HTTP connection
*/
class HttpSession : public std::enable_shared_from_this<HttpSession> {
void doRead();
void onRead(boost::beast::error_code ec, std::size_t);
void onWrite(boost::beast::error_code ec, std::size_t, bool close);
// void sendResponse(boost::beast::http::response<boost::beast::http::string_body> &&response);
public:
using Request = boost::beast::http::request<boost::beast::http::string_body>;
HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state);
template <typename Response>
void reply(const 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);
void run();
private:
boost::beast::tcp_stream m_stream;
boost::beast::flat_buffer m_buffer{std::numeric_limits<std::uint64_t>::max()};
SharedStatePtr m_state;
std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser;
};
#endif // HTTPSESSION_H

64
Server/Listener.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "Listener.h"
#include "HttpSession.h"
#include <boost/asio.hpp>
#include <iostream>
Listener::Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint,
std::shared_ptr<SharedState> const &state)
: m_ioContext(ioc), m_acceptor(ioc), m_state(state) {
boost::beast::error_code ec;
// Open the acceptor
m_acceptor.open(endpoint.protocol(), ec);
if (ec) {
fail(ec, "open");
return;
}
// Allow address reuse
m_acceptor.set_option(boost::asio::socket_base::reuse_address(true), ec);
if (ec) {
fail(ec, "set_option");
return;
}
// Bind to the server address
m_acceptor.bind(endpoint, ec);
if (ec) {
fail(ec, "bind");
return;
}
// Start listening for connections
m_acceptor.listen(boost::asio::socket_base::max_listen_connections, ec);
if (ec) {
fail(ec, "listen");
return;
}
}
void Listener::startAccept() {
// The new connection gets its own strand
auto client = std::make_shared<boost::asio::ip::tcp::socket>(boost::asio::make_strand(m_ioContext));
m_acceptor.async_accept(
*client, [self{shared_from_this()}, client](const boost::system::error_code &ec) { self->onAccept(ec, client); });
}
void Listener::fail(boost::beast::error_code ec, char const *what) {
// Don't report on canceled operations
if (ec == boost::asio::error::operation_aborted) return;
std::cerr << what << ": " << ec.message() << "\n";
}
// Handle a connection
void Listener::onAccept(boost::beast::error_code ec, std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
if (ec) {
if (ec == boost::asio::error::operation_aborted) return;
std::cerr << "accept: " << ec.message() << "\n";
} else { // Launch a new session for this connection
auto session = std::make_shared<HttpSession>(std::move(*socket), m_state);
session->run();
}
startAccept();
}

33
Server/Listener.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef LISTENER_H
#define LISTENER_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <string>
class SharedState;
// Accepts incoming connections and launches the sessions
class Listener : public std::enable_shared_from_this<Listener> {
public:
Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint,
std::shared_ptr<SharedState> const &state);
// Start accepting incoming connections
void startAccept();
inline std::shared_ptr<SharedState> state() const {
return m_state;
}
protected:
void fail(boost::beast::error_code ec, char const *what);
void onAccept(boost::beast::error_code ec, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
private:
boost::asio::io_context &m_ioContext;
boost::asio::ip::tcp::acceptor m_acceptor;
std::shared_ptr<SharedState> m_state;
};
#endif // LISTENER_H

View 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
Server/ResponseUtility.h Normal file
View 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

34
Server/ServiceLogic.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "ServiceLogic.h"
#include <sstream>
namespace ServiceLogic {
boost::beast::http::response<boost::beast::http::string_body>
notFound(const boost::beast::http::request<boost::beast::http::string_body> &request) {
using namespace boost::beast;
http::response<http::string_body> res{http::status::not_found, 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 << "The resource '" << request.target() << "' was not found.";
res.body() = oss.str();
res.prepare_payload();
return res;
}
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;
}
} // namespace ServiceLogic

59
Server/ServiceLogic.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef SERVICELOGIC_H
#define SERVICELOGIC_H
#include "ResponseUtility.h"
#include "SharedState.h"
#include "StringUtility.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 onDownload(SharedStatePtr /*state*/, StringRequest &&request, Send &&send);
template <class Send>
static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send);
template <class Send>
static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix);
template <class Send>
static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send);
template <class Send>
static void onWechat(SharedStatePtr state, StringRequest &&request, Send &&send);
// Returns a not found response
boost::beast::http::response<boost::beast::http::string_body>
notFound(const boost::beast::http::request<boost::beast::http::string_body> &request);
// 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);
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;
}
}; // namespace ServiceLogic
#include "ServiceLogic.inl"
#endif // SERVICELOGIC_H

130
Server/ServiceLogic.inl Normal file
View File

@ -0,0 +1,130 @@
#ifndef SERVICELOGIC_INL
#define SERVICELOGIC_INL
#include "BoostLog.h"
#include "ServiceLogic.h"
#include "WeChatContext/WeChatContext.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 onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send) {
using namespace boost::beast;
boost::urls::url url(request.target());
if (url.path().size() < 12) return;
auto file = url.path().substr(12);
auto root = state->notebookRoot();
auto path = ResponseUtility::pathCat(root, file);
boost::nowide::ifstream ifs(path, boost::nowide::ifstream::binary);
std::vector<char> file_string(std::istreambuf_iterator<char>{ifs}, std::istreambuf_iterator<char>{});
boost::beast::error_code ec;
http::response<boost::beast::http::vector_body<char>> s;
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
s.set(http::field::content_type, ResponseUtility::mimeType(path));
s.keep_alive(request.keep_alive());
s.body() = std::move(file_string);
s.prepare_payload();
return send(std::move(s));
}
template <class Send>
static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix) {
using namespace boost::beast;
boost::urls::url url(request.target());
auto file = url.path().substr(prefix.length());
auto root = state->notebookRoot();
auto path = ResponseUtility::pathCat(root, file);
LOG(info) << "get file: " << path;
if (!std::filesystem::exists(path)) {
return send(notFound(request));
}
boost::nowide::ifstream ifs(path);
std::string file_string(std::istreambuf_iterator<char>{ifs}, std::istreambuf_iterator<char>{});
http::response<boost::beast::http::string_body> s;
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
s.set(http::field::content_type, "text/markdown;charset=UTF-8");
s.keep_alive(request.keep_alive());
s.body() = file_string;
s.prepare_payload();
return send(std::move(s));
}
template <class Send>
void onDownload(SharedStatePtr state, StringRequest &&request, Send &&send) {
LOG(info) << "onDownload";
using namespace boost::beast;
std::string_view file;
if (request.target() == "/download") {
auto requestData = boost::json::parse(request.body());
file = requestData.as_object()["file"].as_string();
} else {
file = request.target().substr(9);
}
auto path = ResponseUtility::pathCat(state->fileRoot(), file);
LOG(info) << "get file: " << path;
// Attempt to open the file
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
// Handle the case where the file doesn't exist
if (ec == boost::system::errc::no_such_file_or_directory) {
return send(notFound(request));
} else if (ec) { // Handle an unknown error
return send(serverError(request, ec.message()));
}
// Cache the size since we need it after the move
auto const size = body.size();
// Respond to GET request
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::content_description, file);
res.set(http::field::accept_charset, "utf-8");
res.content_length(size);
res.keep_alive(request.keep_alive());
return send(std::move(res));
}
template <class Send>
static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) {
using namespace boost::beast;
boost::urls::url url(request.target());
auto context = Amass::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

24
Server/ServiceManager.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef __SERVICEMANAGER_H__
#define __SERVICEMANAGER_H__
#include <MessageManager.h>
#include <Singleton.h>
constexpr auto HeartBeat = "HeartBeat";
constexpr auto SetAlarmClock = "setAlarmClock";
constexpr auto CurrentDatetime = "CurrentDatetime";
constexpr auto PlayRandomMusic = "PlayRandomMusic";
constexpr auto NotifyServerChan = MessageManager::Message<const std::string &>("NotifyServerChan");
constexpr auto TextToSpeech = MessageManager::Message<const std::string &>("TextToSpeech");
constexpr auto StopPlayMusic = MessageManager::Message<>("StopPlayMusic");
using SetAlarmClockService = void(uint8_t, uint8_t);
using CurrentDatetimeService = void();
using PlayRandomMusicService = void();
class ServiceManager : public MessageManager {
friend class Amass::Singleton<MessageManager>;
};
#endif // __SERVICEMANAGER_H__

107
Server/SharedState.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "SharedState.h"
#include "HttpSession.h"
#include "ServiceLogic.h"
#include "WeChatContext/CorporationContext.h"
#include "WebsocketSession.h"
SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_root)
: m_ioContext(ioContext), m_router{std::make_shared<UrlRouter<Handler>>()}, m_docRoot(std::move(doc_root)) {
m_router->insert("/", [](HttpSession &session, const Request &request, const TemplateMatches &matches) {
// Send content message to client and wait to receive next request
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
});
m_router->insert("/wechat/{session*}",
[this](HttpSession &session, const Request &request, const TemplateMatches &matches) {
ServiceLogic::onWechat(shared_from_this(), request,
[&session](auto &&response) { session.reply(std::move(response)); });
});
// m_router->all(R"(^/download/.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) {
// ServiceLogic::onDownload(shared_from_this(), std::move(request),
// [&context](auto &&response) { context.send(std::move(response)); });
// });
// m_router->all(R"(^/blog/list$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) {
// ServiceLogic::onGetBlogList(shared_from_this(), std::move(request),
// [&context](auto &&response) { context.send(response); });
// });
// m_router->all(R"(^/blog/image/.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) {
// ServiceLogic::onGetBlogImage(shared_from_this(), std::move(request),
// [&context](auto &&response) { context.send(std::move(response)); });
// });
// m_router->all(R"(^/blog/content.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context)
// {
// ServiceLogic::onGetBlogContent(
// shared_from_this(), std::move(request), [&context](auto &&response) { context.send(std::move(response));
// },
// "/blog/content");
// });
m_router->insert(
"/trigger-ci.hook", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) {
LOG(info) << "webhook: " << request;
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
});
m_router->insert("/notify", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) {
auto corp = Amass::Singleton<CorporationContext>::instance();
corp->notify(request);
session.reply(
ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
});
}
const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path,
TemplateMatchStorageBase &matches) const noexcept {
return m_router->find(path, matches);
}
std::string_view SharedState::galleryRoot() const noexcept {
return m_galleryRoot;
}
void SharedState::setFileRoot(const std::string_view &root) {
if (m_fileRoot == root) return;
m_fileRoot = root;
}
std::string_view SharedState::notebookRoot() const {
return m_notebookRoot;
}
void SharedState::setNotebookRoot(const std::string_view &root) {
if (m_notebookRoot == root) return;
m_notebookRoot = root;
}
void SharedState::join(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(mutex_);
sessions_.insert(session);
}
void SharedState::leave(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(mutex_);
sessions_.erase(session);
}
void SharedState::send(std::string message) {
// Put the message in a shared pointer so we can re-use it for each client
auto const ss = std::make_shared<std::string const>(std::move(message));
// Make a local list of all the weak pointers representing
// the sessions, so we can do the actual sending without
// holding the mutex:
std::vector<std::weak_ptr<WebSocketSession>> v;
{
std::lock_guard<std::mutex> lock(mutex_);
v.reserve(sessions_.size());
for (auto p : sessions_) v.emplace_back(p->weak_from_this());
}
// For each session in our local list, try to acquire a strong
// pointer. If successful, then send the message on that session.
for (auto const &wp : v)
if (auto sp = wp.lock()) sp->send(ss);
}

65
Server/SharedState.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef SHAREDSTATE_H
#define SHAREDSTATE_H
#include "TemplateMatchs.h"
#include "UrlRouter.h"
#include <boost/asio/io_context.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/smart_ptr.hpp>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_set>
class HttpSession;
class WebSocketSession;
// Represents the shared server state
class SharedState:public std::enable_shared_from_this<SharedState> {
// This mutex synchronizes all access to sessions_
std::mutex mutex_;
// Keep a list of all the connected clients
std::unordered_set<WebSocketSession *> sessions_;
public:
using Request = boost::beast::http::request<boost::beast::http::string_body>;
using Handler = std::function<void(HttpSession &, const Request &, const TemplateMatches &)>;
SharedState(boost::asio::io_context &ioContext,std::string doc_root);
const Handler *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &matches) const noexcept ;
inline std::string_view docRoot() const noexcept {
return m_docRoot;
}
std::string_view galleryRoot() const noexcept;
inline std::string_view fileRoot() const {
return m_fileRoot;
}
void setFileRoot(const std::string_view &root);
std::string_view notebookRoot() const;
void setNotebookRoot(const std::string_view &root);
void join(WebSocketSession *session);
void leave(WebSocketSession *session);
/**
* @brief Broadcast a message to all websocket client sessions
*/
void send(std::string message);
private:
boost::asio::io_context &m_ioContext;
std::shared_ptr <UrlRouter<Handler>> m_router;
std::string m_docRoot;
std::string m_galleryRoot = "/root/photos";
std::string m_fileRoot;
std::string m_notebookRoot;
};
using SharedStatePtr = std::shared_ptr<SharedState>;
#endif // SHAREDSTATE_H

78
Server/UdpServer.cpp Normal file
View File

@ -0,0 +1,78 @@
#include "UdpServer.h"
#include "BoostLog.h"
#include "boost/endian.hpp"
#include <random>
#include <sstream>
UdpServer::UdpServer(boost::asio::io_context &io_context)
: m_socket(io_context, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 8088)), m_reveiveBuffer(512),
m_timer(io_context) {
// start recieve.
m_socket.async_receive_from(
boost::asio::buffer(m_reveiveBuffer), m_remotePoint,
std::bind(&UdpServer::reveiveHandler, this, std::placeholders::_1, std::placeholders::_2));
sendData();
boost::system::error_code error;
auto address = boost::asio::ip::address::from_string("127.0.0.1", error);
if (error) {
LOG(error) << error.message();
} else {
m_remotePoint = boost::asio::ip::udp::endpoint(address, 1234);
}
}
void UdpServer::reveiveHandler(const boost::system::error_code &error, std::size_t bytes_transferred) {
LOG(info) << "Received byte size: " << bytes_transferred
<< ",remote ip address: " << m_remotePoint.address().to_string() << std::endl;
if (error) {
LOG(error) << error.message();
return;
}
handleReceivedBuffer(m_reveiveBuffer);
// continue recieve next datagram.
m_socket.async_receive_from(
boost::asio::buffer(m_reveiveBuffer), m_remotePoint,
std::bind(&UdpServer::reveiveHandler, this, std::placeholders::_1, std::placeholders::_2));
}
void UdpServer::sendHandler(const boost::system::error_code &error, std::size_t bytes_transferred) {
if (error) {
LOG(error) << error.message();
}
}
void UdpServer::handleReceivedBuffer(std::vector<char> &data) {
}
void UdpServer::sendData() {
using namespace std::chrono_literals;
static uint32_t index = 0;
m_timer.expires_after(1ms);
m_timer.async_wait([this](const boost::system::error_code &) {
std::random_device rd; // 将用于获得随机数引擎的种子
std::mt19937 gen(rd()); // 以 rd() 播种的标准 mersenne_twister_engine
std::uniform_int_distribution<uint32_t> distribution(std::numeric_limits<uint32_t>::min(),
std::numeric_limits<uint32_t>::max());
std::array<char, 408> data;
data[0] = 0xAA;
data[1] = 0xFF;
data[2] = 0x55;
data[3] = 0x00;
auto bigEndian = reinterpret_cast<uint32_t *>(&data[4]);
*bigEndian++ = boost::endian::native_to_big(index++);
for (size_t i = 0; i < 100; i++) {
*bigEndian++ = boost::endian::native_to_big(distribution(gen));
}
m_socket.async_send_to(boost::asio::buffer(data), m_remotePoint,
std::bind(&UdpServer::sendHandler, this, std::placeholders::_1, std::placeholders::_2));
sendData();
});
}

36
Server/UdpServer.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef UDPSERVER_H
#define UDPSERVER_H
#include "boost/asio.hpp"
class UdpServer {
public:
UdpServer(boost::asio::io_context &io_context);
protected:
/**
* @brief reveiveHandler
* @param error Result of operation.
* @param bytes_transferred Number of bytes received.
*/
void reveiveHandler(const boost::system::error_code &error, std::size_t bytes_transferred);
/**
* @brief sendHandler
* @param error Result of operation.
* @param bytes_transferred Number of bytes sent.
*/
void sendHandler(const boost::system::error_code &error, std::size_t bytes_transferred);
void handleReceivedBuffer(std::vector<char> &data);
void sendData();
private:
boost::asio::ip::udp::socket m_socket;
boost::asio::ip::udp::endpoint m_remotePoint; //客户端端点
std::vector<char> m_reveiveBuffer;
boost::asio::steady_timer m_timer;
};
#endif // UDPSERVER_H

View File

@ -0,0 +1,113 @@
#include "CorporationContext.h"
#include "../ServiceManager.h"
#include "BoostLog.h"
#include "NetworkUtility.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>
CorporationContext::CorporationContext(boost::asio::io_context &ioContext)
: m_ioContext(ioContext), m_timer(ioContext) {
boost::asio::defer([this]() { updateAccessToken(); });
auto manager = Amass::Singleton<ServiceManager>::instance();
if (manager)
manager->registerTopic(NotifyServerChan,
[this](const std::string &text) { sendMessage(MessageType::Text, text); });
}
void CorporationContext::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 CorporationContext::notify(const RequestType &request) {
boost::json::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 CorporationContext::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;
}
}

View File

@ -0,0 +1,41 @@
#ifndef __CORPORATIONCONTEXT_H__
#define __CORPORATIONCONTEXT_H__
#include <boost/asio/steady_timer.hpp>
#include <boost/beast/http/string_body.hpp>
class CorporationContext {
public:
enum MessageType {
Text,
Markdown,
};
using RequestType = boost::beast::http::request<boost::beast::http::string_body>;
CorporationContext(boost::asio::io_context &ioContext);
void sendMessage(MessageType type,const std::string &message);
/**
* @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:
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;
};
#endif // __CORPORATIONCONTEXT_H__

View File

@ -0,0 +1,184 @@
#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([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);
});
}

View File

@ -0,0 +1,49 @@
#ifndef WECHATCONTEXT_H
#define WECHATCONTEXT_H
#include "Singleton.h"
#include <boost/asio/steady_timer.hpp>
#include <memory>
#include <unordered_map>
class WeChatSession;
class WeChatContext : public std::enable_shared_from_this<WeChatContext> {
friend class Amass::Singleton<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

View 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>();
}

View 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__

View File

@ -0,0 +1,83 @@
#include "WebsocketSession.h"
#include <iostream>
WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state)
: m_ws(std::move(socket)), m_state(state) {}
WebSocketSession::~WebSocketSession() {
// Remove this session from the list of active sessions
m_state->leave(this);
}
void WebSocketSession::onAccept(boost::beast::error_code ec) {
// Handle the error, if any
if (ec) {
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
std::cerr << "accept: " << ec.message() << "\n";
return;
}
// Add this session to the list of active sessions
m_state->join(this);
// Read a message
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this()));
}
void WebSocketSession::on_read(boost::beast::error_code ec, std::size_t) {
// Handle the error, if any
if (ec) {
// Don't report these
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
LOG(error) << "read: " << ec.message();
return;
}
LOG(info) << boost::beast::buffers_to_string(m_buffer.data());
// Send to all connections
m_state->send(boost::beast::buffers_to_string(m_buffer.data()));
// Clear the buffer
m_buffer.consume(m_buffer.size());
// Read another message
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this()));
}
void WebSocketSession::send(std::shared_ptr<std::string const> const &ss) {
// Post our work to the strand, this ensures
// that the members of `this` will not be
// accessed concurrently.
m_ws.text();
boost::asio::post(m_ws.get_executor(),
boost::beast::bind_front_handler(&WebSocketSession::onSend, shared_from_this(), ss));
}
void WebSocketSession::onSend(std::shared_ptr<std::string const> const &ss) {
// Always add to queue
m_queue.push_back(ss);
// Are we already writing?
if (m_queue.size() > 1) return;
// We are not currently writing, so send this immediately
m_ws.async_write(boost::asio::buffer(*m_queue.front()),
boost::beast::bind_front_handler(&WebSocketSession::on_write, shared_from_this()));
}
void WebSocketSession::on_write(boost::beast::error_code ec, std::size_t) {
// Handle the error, if any
if (ec) {
// Don't report these
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
std::cerr << "write: " << ec.message() << "\n";
return;
}
// Remove the string from the queue
m_queue.erase(m_queue.begin());
// Send the next message if any
if (!m_queue.empty())
m_ws.async_write(boost::asio::buffer(*m_queue.front()),
boost::beast::bind_front_handler(&WebSocketSession::on_write, shared_from_this()));
}

56
Server/WebsocketSession.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef WEBSOCKETSESSION_H
#define WEBSOCKETSESSION_H
#include "BoostLog.h"
#include "SharedState.h"
#include <boost/beast.hpp>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
class SharedState;
/**
* @brief Represents an active WebSocket connection to the server
*/
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> {
public:
WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state);
~WebSocketSession();
template <class Body, class Allocator>
void run(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> request) {
using namespace boost::beast::http;
using namespace boost::beast::websocket;
// Set suggested timeout settings for the websocket
m_ws.set_option(stream_base::timeout::suggested(boost::beast::role_type::server));
// Set a decorator to change the Server of the handshake
m_ws.set_option(stream_base::decorator([](response_type &response) {
response.set(field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-chat-multi");
}));
// LOG(info) << request.base().target(); //get path
// Accept the websocket handshake
m_ws.async_accept(request, [self{shared_from_this()}](boost::beast::error_code ec) { self->onAccept(ec); });
}
// Send a message
void send(std::shared_ptr<std::string const> const &ss);
protected:
void onAccept(boost::beast::error_code ec);
void on_read(boost::beast::error_code ec, std::size_t bytes_transferred);
void on_write(boost::beast::error_code ec, std::size_t bytes_transferred);
void onSend(std::shared_ptr<std::string const> const &ss);
private:
boost::beast::flat_buffer m_buffer;
boost::beast::websocket::stream<boost::beast::tcp_stream> m_ws;
std::shared_ptr<SharedState> m_state;
std::vector<std::shared_ptr<std::string const>> m_queue;
};
#endif // WEBSOCKETSESSION_H

26
Server/conf/fastcgi.conf Normal file
View File

@ -0,0 +1,26 @@
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@ -0,0 +1,26 @@
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@ -0,0 +1,25 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@ -0,0 +1,25 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

109
Server/conf/koi-utf Normal file
View File

@ -0,0 +1,109 @@
# This map is not a full koi8-r <> utf8 map: it does not contain
# box-drawing and some other characters. Besides this map contains
# several koi8-u and Byelorussian letters which are not in koi8-r.
# If you need a full and standard map, use contrib/unicode2nginx/koi-utf
# map instead.
charset_map koi8-r utf-8 {
80 E282AC ; # euro
95 E280A2 ; # bullet
9A C2A0 ; # &nbsp;
9E C2B7 ; # &middot;
A3 D191 ; # small yo
A4 D194 ; # small Ukrainian ye
A6 D196 ; # small Ukrainian i
A7 D197 ; # small Ukrainian yi
AD D291 ; # small Ukrainian soft g
AE D19E ; # small Byelorussian short u
B0 C2B0 ; # &deg;
B3 D081 ; # capital YO
B4 D084 ; # capital Ukrainian YE
B6 D086 ; # capital Ukrainian I
B7 D087 ; # capital Ukrainian YI
B9 E28496 ; # numero sign
BD D290 ; # capital Ukrainian soft G
BE D18E ; # capital Byelorussian short U
BF C2A9 ; # (C)
C0 D18E ; # small yu
C1 D0B0 ; # small a
C2 D0B1 ; # small b
C3 D186 ; # small ts
C4 D0B4 ; # small d
C5 D0B5 ; # small ye
C6 D184 ; # small f
C7 D0B3 ; # small g
C8 D185 ; # small kh
C9 D0B8 ; # small i
CA D0B9 ; # small j
CB D0BA ; # small k
CC D0BB ; # small l
CD D0BC ; # small m
CE D0BD ; # small n
CF D0BE ; # small o
D0 D0BF ; # small p
D1 D18F ; # small ya
D2 D180 ; # small r
D3 D181 ; # small s
D4 D182 ; # small t
D5 D183 ; # small u
D6 D0B6 ; # small zh
D7 D0B2 ; # small v
D8 D18C ; # small soft sign
D9 D18B ; # small y
DA D0B7 ; # small z
DB D188 ; # small sh
DC D18D ; # small e
DD D189 ; # small shch
DE D187 ; # small ch
DF D18A ; # small hard sign
E0 D0AE ; # capital YU
E1 D090 ; # capital A
E2 D091 ; # capital B
E3 D0A6 ; # capital TS
E4 D094 ; # capital D
E5 D095 ; # capital YE
E6 D0A4 ; # capital F
E7 D093 ; # capital G
E8 D0A5 ; # capital KH
E9 D098 ; # capital I
EA D099 ; # capital J
EB D09A ; # capital K
EC D09B ; # capital L
ED D09C ; # capital M
EE D09D ; # capital N
EF D09E ; # capital O
F0 D09F ; # capital P
F1 D0AF ; # capital YA
F2 D0A0 ; # capital R
F3 D0A1 ; # capital S
F4 D0A2 ; # capital T
F5 D0A3 ; # capital U
F6 D096 ; # capital ZH
F7 D092 ; # capital V
F8 D0AC ; # capital soft sign
F9 D0AB ; # capital Y
FA D097 ; # capital Z
FB D0A8 ; # capital SH
FC D0AD ; # capital E
FD D0A9 ; # capital SHCH
FE D0A7 ; # capital CH
FF D0AA ; # capital hard sign
}

103
Server/conf/koi-win Normal file
View File

@ -0,0 +1,103 @@
charset_map koi8-r windows-1251 {
80 88 ; # euro
95 95 ; # bullet
9A A0 ; # &nbsp;
9E B7 ; # &middot;
A3 B8 ; # small yo
A4 BA ; # small Ukrainian ye
A6 B3 ; # small Ukrainian i
A7 BF ; # small Ukrainian yi
AD B4 ; # small Ukrainian soft g
AE A2 ; # small Byelorussian short u
B0 B0 ; # &deg;
B3 A8 ; # capital YO
B4 AA ; # capital Ukrainian YE
B6 B2 ; # capital Ukrainian I
B7 AF ; # capital Ukrainian YI
B9 B9 ; # numero sign
BD A5 ; # capital Ukrainian soft G
BE A1 ; # capital Byelorussian short U
BF A9 ; # (C)
C0 FE ; # small yu
C1 E0 ; # small a
C2 E1 ; # small b
C3 F6 ; # small ts
C4 E4 ; # small d
C5 E5 ; # small ye
C6 F4 ; # small f
C7 E3 ; # small g
C8 F5 ; # small kh
C9 E8 ; # small i
CA E9 ; # small j
CB EA ; # small k
CC EB ; # small l
CD EC ; # small m
CE ED ; # small n
CF EE ; # small o
D0 EF ; # small p
D1 FF ; # small ya
D2 F0 ; # small r
D3 F1 ; # small s
D4 F2 ; # small t
D5 F3 ; # small u
D6 E6 ; # small zh
D7 E2 ; # small v
D8 FC ; # small soft sign
D9 FB ; # small y
DA E7 ; # small z
DB F8 ; # small sh
DC FD ; # small e
DD F9 ; # small shch
DE F7 ; # small ch
DF FA ; # small hard sign
E0 DE ; # capital YU
E1 C0 ; # capital A
E2 C1 ; # capital B
E3 D6 ; # capital TS
E4 C4 ; # capital D
E5 C5 ; # capital YE
E6 D4 ; # capital F
E7 C3 ; # capital G
E8 D5 ; # capital KH
E9 C8 ; # capital I
EA C9 ; # capital J
EB CA ; # capital K
EC CB ; # capital L
ED CC ; # capital M
EE CD ; # capital N
EF CE ; # capital O
F0 CF ; # capital P
F1 DF ; # capital YA
F2 D0 ; # capital R
F3 D1 ; # capital S
F4 D2 ; # capital T
F5 D3 ; # capital U
F6 C6 ; # capital ZH
F7 C2 ; # capital V
F8 DC ; # capital soft sign
F9 DB ; # capital Y
FA C7 ; # capital Z
FB D8 ; # capital SH
FC DD ; # capital E
FD D9 ; # capital SHCH
FE D7 ; # capital CH
FF DA ; # capital hard sign
}

97
Server/conf/mime.types Normal file
View File

@ -0,0 +1,97 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@ -0,0 +1,97 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

239
Server/conf/nginx.conf Normal file
View File

@ -0,0 +1,239 @@
user root;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
rewrite_log on;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_types application/octet-stream text/markdown text/plain application/json application/x-javascript text/css application/xml text/javascript application/javascript application/x-httpd-php image/jpeg image/gif image/png;
upstream local {
server 127.0.0.1:8080;
}
upstream wiznote {
server 127.0.0.1:8081;
}
upstream gitea {
server 127.0.0.1:8082;
}
upstream twikoo {
server 127.0.0.1:8089;
}
upstream drone_server {
server 127.0.0.1:1080;
}
init_by_lua_file lua/settings.lua;
server {
listen 443 ssl;
server_name wiznote.amass.fun;
ssl_certificate cert/9890678_wiznote.amass.fun.pem;
ssl_certificate_key cert/9890678_wiznote.amass.fun.key;
ssl_session_timeout 5m; #缓存有效期
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议
ssl_prefer_server_ciphers on; #使用服务器端的首选算法
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header x-wiz-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://wiznote;
}
}
server {
listen 443 ssl;
server_name gitea.amass.fun;
ssl_certificate cert/gitea.amass.fun.pem;
ssl_certificate_key cert/gitea.amass.fun.key;
ssl_session_timeout 5m; #缓存有效期
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议
ssl_prefer_server_ciphers on; #使用服务器端的首选算法
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://gitea;
}
}
server {
listen 443 ssl;
server_name drone.amass.fun;
ssl_certificate cert/8839053_drone.amass.fun.pem;
ssl_certificate_key cert/8839053_drone.amass.fun.key;
ssl_session_timeout 5m; #缓存有效期
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议
ssl_prefer_server_ciphers on; #使用服务器端的首选算法
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://drone_server;
}
}
server {
listen 443 ssl;
server_name amass.fun;
ssl_certificate cert/6232035_amass.fun.pem;
ssl_certificate_key cert/6232035_amass.fun.key;
ssl_session_timeout 5m; #缓存有效期
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议
ssl_prefer_server_ciphers on; #使用服务器端的首选算法
location / {
root amass_blog;
index index.html index.htm;
}
location /lua {
default_type text/html;
content_by_lua_file lua/helloworld.lua;
}
location = /blog/login {
content_by_lua_file lua/login.lua;
}
location = /blog/profile {
content_by_lua_file lua/profile.lua;
}
location /video {
access_by_lua_file lua/access.lua;
proxy_pass http://local;
}
location /phtot_gallery {
proxy_pass http://local;
}
location = /blog/list {
header_filter_by_lua_block {
ngx.header.content_length = nil
}
body_filter_by_lua_file lua/blog_list.lua; # 过滤掉 隐私文件夹
proxy_pass http://local;
}
location = /search/website_collections {
content_by_lua_file lua/request_website_collections.lua;
}
location ~ /trigger-ci.+$ {
proxy_pass http://local;
}
location ~ /notify.*$ {
proxy_pass http://local;
}
location /InstallerRepository {
root .;
index index.html index.htm;
}
location /Younger/ChatRoom {
proxy_pass http://local;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1200s;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location ^~ /index/ {
try_files $uri /index.html;
}
location /wechat {
proxy_pass http://local;
}
location /twikoo {
proxy_pass http://twikoo;
}
}
server {
listen 80;
server_name wiznote.amass.fun;
rewrite ^(.*)$ https://wiznote.amass.fun$1 permanent;
}
server {
listen 80;
server_name gitea.amass.fun;
rewrite ^(.*)$ https://gitea.amass.fun$1 permanent;
}
server {
listen 80;
server_name drone.amass.fun;
rewrite ^(.*)$ https://drone.amass.fun$1 permanent;
}
server {
listen 80;
server_name amass.fun;
location /resource {
root .;
}
location / {
rewrite ^(.*)$ https://amass.fun$1 permanent;
}
}
}

View File

@ -0,0 +1,117 @@
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}

17
Server/conf/scgi_params Normal file
View File

@ -0,0 +1,17 @@
scgi_param REQUEST_METHOD $request_method;
scgi_param REQUEST_URI $request_uri;
scgi_param QUERY_STRING $query_string;
scgi_param CONTENT_TYPE $content_type;
scgi_param DOCUMENT_URI $document_uri;
scgi_param DOCUMENT_ROOT $document_root;
scgi_param SCGI 1;
scgi_param SERVER_PROTOCOL $server_protocol;
scgi_param REQUEST_SCHEME $scheme;
scgi_param HTTPS $https if_not_empty;
scgi_param REMOTE_ADDR $remote_addr;
scgi_param REMOTE_PORT $remote_port;
scgi_param SERVER_PORT $server_port;
scgi_param SERVER_NAME $server_name;

View File

@ -0,0 +1,17 @@
scgi_param REQUEST_METHOD $request_method;
scgi_param REQUEST_URI $request_uri;
scgi_param QUERY_STRING $query_string;
scgi_param CONTENT_TYPE $content_type;
scgi_param DOCUMENT_URI $document_uri;
scgi_param DOCUMENT_ROOT $document_root;
scgi_param SCGI 1;
scgi_param SERVER_PROTOCOL $server_protocol;
scgi_param REQUEST_SCHEME $scheme;
scgi_param HTTPS $https if_not_empty;
scgi_param REMOTE_ADDR $remote_addr;
scgi_param REMOTE_PORT $remote_port;
scgi_param SERVER_PORT $server_port;
scgi_param SERVER_NAME $server_name;

17
Server/conf/uwsgi_params Normal file
View File

@ -0,0 +1,17 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

View File

@ -0,0 +1,17 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

126
Server/conf/win-utf Normal file
View File

@ -0,0 +1,126 @@
# This map is not a full windows-1251 <> utf8 map: it does not
# contain Serbian and Macedonian letters. If you need a full map,
# use contrib/unicode2nginx/win-utf map instead.
charset_map windows-1251 utf-8 {
82 E2809A ; # single low-9 quotation mark
84 E2809E ; # double low-9 quotation mark
85 E280A6 ; # ellipsis
86 E280A0 ; # dagger
87 E280A1 ; # double dagger
88 E282AC ; # euro
89 E280B0 ; # per mille
91 E28098 ; # left single quotation mark
92 E28099 ; # right single quotation mark
93 E2809C ; # left double quotation mark
94 E2809D ; # right double quotation mark
95 E280A2 ; # bullet
96 E28093 ; # en dash
97 E28094 ; # em dash
99 E284A2 ; # trade mark sign
A0 C2A0 ; # &nbsp;
A1 D18E ; # capital Byelorussian short U
A2 D19E ; # small Byelorussian short u
A4 C2A4 ; # currency sign
A5 D290 ; # capital Ukrainian soft G
A6 C2A6 ; # borken bar
A7 C2A7 ; # section sign
A8 D081 ; # capital YO
A9 C2A9 ; # (C)
AA D084 ; # capital Ukrainian YE
AB C2AB ; # left-pointing double angle quotation mark
AC C2AC ; # not sign
AD C2AD ; # soft hypen
AE C2AE ; # (R)
AF D087 ; # capital Ukrainian YI
B0 C2B0 ; # &deg;
B1 C2B1 ; # plus-minus sign
B2 D086 ; # capital Ukrainian I
B3 D196 ; # small Ukrainian i
B4 D291 ; # small Ukrainian soft g
B5 C2B5 ; # micro sign
B6 C2B6 ; # pilcrow sign
B7 C2B7 ; # &middot;
B8 D191 ; # small yo
B9 E28496 ; # numero sign
BA D194 ; # small Ukrainian ye
BB C2BB ; # right-pointing double angle quotation mark
BF D197 ; # small Ukrainian yi
C0 D090 ; # capital A
C1 D091 ; # capital B
C2 D092 ; # capital V
C3 D093 ; # capital G
C4 D094 ; # capital D
C5 D095 ; # capital YE
C6 D096 ; # capital ZH
C7 D097 ; # capital Z
C8 D098 ; # capital I
C9 D099 ; # capital J
CA D09A ; # capital K
CB D09B ; # capital L
CC D09C ; # capital M
CD D09D ; # capital N
CE D09E ; # capital O
CF D09F ; # capital P
D0 D0A0 ; # capital R
D1 D0A1 ; # capital S
D2 D0A2 ; # capital T
D3 D0A3 ; # capital U
D4 D0A4 ; # capital F
D5 D0A5 ; # capital KH
D6 D0A6 ; # capital TS
D7 D0A7 ; # capital CH
D8 D0A8 ; # capital SH
D9 D0A9 ; # capital SHCH
DA D0AA ; # capital hard sign
DB D0AB ; # capital Y
DC D0AC ; # capital soft sign
DD D0AD ; # capital E
DE D0AE ; # capital YU
DF D0AF ; # capital YA
E0 D0B0 ; # small a
E1 D0B1 ; # small b
E2 D0B2 ; # small v
E3 D0B3 ; # small g
E4 D0B4 ; # small d
E5 D0B5 ; # small ye
E6 D0B6 ; # small zh
E7 D0B7 ; # small z
E8 D0B8 ; # small i
E9 D0B9 ; # small j
EA D0BA ; # small k
EB D0BB ; # small l
EC D0BC ; # small m
ED D0BD ; # small n
EE D0BE ; # small o
EF D0BF ; # small p
F0 D180 ; # small r
F1 D181 ; # small s
F2 D182 ; # small t
F3 D183 ; # small u
F4 D184 ; # small f
F5 D185 ; # small kh
F6 D186 ; # small ts
F7 D187 ; # small ch
F8 D188 ; # small sh
F9 D189 ; # small shch
FA D18A ; # small hard sign
FB D18B ; # small y
FC D18C ; # small soft sign
FD D18D ; # small e
FE D18E ; # small yu
FF D18F ; # small ya
}

183
Server/main.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "AlarmClockServer.h"
#include "BoostLog.h"
#include "IoContext.h"
#include "Listener.h"
#include "ProxyListener.h"
#include "ServiceManager.h"
#include "SharedState.h"
#include "UdpServer.h"
#include "WeChatContext/CorporationContext.h"
#include "WeChatContext/WeChatContext.h"
#include <boost/program_options.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <filesystem>
void initSettings();
int main(int argc, char const *argv[]) {
initBoostLog("HttpServer");
auto manager = Amass::Singleton<ServiceManager>::instance<Amass::Construct>();
boost::program_options::options_description description("Allowed options");
// clang-format off
description.add_options()
("help,h", "produce help message.")
("prefix", boost::program_options::value<std::string>(),"set prefix path (default: ${pwd} )");
// clang-format on
boost::program_options::variables_map values;
try {
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, description), values);
boost::program_options::notify(values);
} catch (const boost::program_options::invalid_command_line_syntax &e) {
LOG(fatal) << e.what();
std::exit(-1);
}
if (values.count("help")) {
std::cout << description << std::endl;
std::exit(0);
}
std::error_code error;
auto prefix = std::filesystem::current_path(error);
if (error) {
LOG(fatal) << "cannot get current path,reason: " << error.message();
return -1;
}
if (values.count("prefix")) {
prefix = values["prefix"].as<std::string>();
if (prefix.empty() || !std::filesystem::exists(prefix)) {
LOG(fatal) << "working directory: " << prefix << " is not exists.";
return -1;
}
std::filesystem::current_path(prefix, error);
LOG_IF(fatal, error) << "cannot set current path,reason: " << error.message();
}
initSettings();
std::string server = "0.0.0.0";
boost::property_tree::ptree ptree;
boost::property_tree::read_ini("settings.ini", ptree);
if (ptree.count("server") > 0) {
server = ptree.get<std::string>("server");
}
auto port = ptree.get_optional<uint16_t>("port");
auto docRoot = ptree.get<std::string>("docRoot");
if (docRoot.empty()) {
LOG(fatal) << "please set docRoot.";
std::exit(101);
} else if (!std::filesystem::exists(docRoot)) {
LOG(fatal) << "document root: " << docRoot << " is not exists...";
std::exit(102);
}
auto fileRoot = ptree.get<std::string>("fileRoot");
if (fileRoot.empty()) {
LOG(fatal) << "please set fileRoot.";
std::exit(103);
} else if (!std::filesystem::exists(fileRoot)) {
LOG(fatal) << "file root: " << fileRoot << " is not exists...";
std::exit(104);
}
auto notebookRoot = ptree.get<std::string>("notebookRoot");
if (notebookRoot.empty()) {
LOG(fatal) << "please set notebookRoot.";
std::exit(105);
} else if (!std::filesystem::exists(notebookRoot)) {
LOG(fatal) << "notebook root: " << notebookRoot << " is not exists...";
std::exit(106);
}
if (!port) {
LOG(fatal) << "port is not a number.";
std::exit(255);
}
auto threads = ptree.get<unsigned int>("threads");
if (threads <= 0 || threads > std::thread::hardware_concurrency()) threads = std::thread::hardware_concurrency();
BOOST_ASSERT_MSG(!server.empty(), "server.empty() == true");
auto ioContext = Amass::Singleton<IoContext>::instance<Amass::Construct>(threads);
auto address = boost::asio::ip::make_address(server);
auto listener = std::make_shared<Listener>(*ioContext->ioContext(), boost::asio::ip::tcp::endpoint{address, *port},
std::make_shared<SharedState>(*ioContext->ioContext(), docRoot));
listener->startAccept();
auto state = listener->state();
state->setFileRoot(fileRoot);
state->setNotebookRoot(notebookRoot);
auto alarmClockServer = Amass::Singleton<AlarmClockServer>::instance<Amass::Construct>(*ioContext->ioContext());
alarmClockServer->listen(server, "8089");
auto wechatContext = Amass::Singleton<WeChatContext>::instance<Amass::Construct>(*ioContext->ioContext());
auto corpContext = Amass::Singleton<CorporationContext>::instance<Amass::Construct>(*ioContext->ioContext());
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency() << ",threads: " << threads;
LOG(info) << "working directory: " << prefix.generic_string();
LOG(info) << "server: " << server << ",port: " << *port;
LOG(info) << "document root: " << state->docRoot();
LOG(info) << "notebook root: " << state->notebookRoot();
// Capture SIGINT and SIGTERM to perform a clean shutdown
#ifndef WIN32
boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM, SIGHUP);
#else
boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM);
#endif
signals.async_wait([&ioContext](boost::system::error_code const &, int signal) {
// Stop the io_context. This will cause run()
// to return immediately, eventually destroying the
// io_context and any remaining handlers in it.
LOG(info) << "capture " << (signal == SIGINT ? "SIGINT" : "SIGTERM") << ",stop!";
ioContext->ioContext()->stop();
});
auto udpServer = std::make_shared<UdpServer>(*ioContext->ioContext());
auto proxyAddress = boost::asio::ip::make_address(server);
uint16_t proxyPort = 41091;
auto proxy = std::make_shared<ProxyListener>(*ioContext->ioContext(),
boost::asio::ip::tcp::endpoint{proxyAddress, proxyPort});
boost::system::error_code perror;
proxy->run(perror);
LOG(info) << "server start successful ...";
ioContext->run<IoContext::Mode::Synchronous>();
LOG(info) << "server exit successful ...";
return EXIT_SUCCESS;
}
void initSettings() {
boost::property_tree::ptree ptree;
if (std::filesystem::exists(std::filesystem::path("settings.ini")))
boost::property_tree::read_ini("settings.ini", ptree);
if (ptree.find("server") == ptree.not_found()) {
ptree.put("server", "0.0.0.0");
}
if (ptree.find("port") == ptree.not_found()) {
ptree.put("port", 8080);
}
if (ptree.find("docRoot") == ptree.not_found()) {
ptree.put("docRoot", ".");
}
if (ptree.find("fileRoot") == ptree.not_found()) {
ptree.put("fileRoot", ".");
}
if (ptree.find("notebookRoot") == ptree.not_found()) {
ptree.put("notebookRoot", ".");
}
if (ptree.find("threads") == ptree.not_found()) {
ptree.put("threads", std::thread::hardware_concurrency());
}
boost::property_tree::write_ini("settings.ini", ptree);
}

66
resource/deploy.sh Executable file
View File

@ -0,0 +1,66 @@
#!/bin/bash
base_path=$(pwd)
build_path=${base_path}/build
libraries_root="/opt/Libraries"
server_location=/root/HttpServer
function cmake_scan() {
if [ ! -d ${build_path} ]; then
mkdir ${build_path}
fi
/opt/Qt/Tools/CMake/bin/cmake \
-G Ninja \
-S ${base_path} \
-B ${build_path} \
-DCMAKE_BUILD_TYPE=Debug \
-DBOOST_ROOT=${libraries_root}/boost_1_82_0 \
-DZeroMQ_ROOT=${libraries_root}/zeromq-4.3.4_debug
}
function build() {
if [ ! -f "${build_path}/CMakeCache.txt" ]; then
cmake_scan
fi
if [ $? -ne 0 ]; then
exit 1
fi
/opt/Qt/Tools/CMake/bin/cmake \
--build ${build_path} \
--target all
}
function deploy_backend() {
build
if [ $? -ne 0 ]; then
echo "build backend failed ..."
exit 1
fi
rsync -azv build/Server/HttpServer Server/conf root@amass.fun:${server_location}
ssh root@amass.fun "pkill HttpServer; source /etc/profile && \
nginx -p ${server_location} -s reload && \
cd ${server_location}; \
nohup ./HttpServer >logs/HttpServer.log 2>&1 &"
}
function deploy() {
deploy_backend
}
function main() {
local cmd=$1
shift 1
case $cmd in
deploy)
deploy
;;
build)
build
;;
*)
build
;;
esac
}
main $@

6
resource/notify.tpl Normal file
View File

@ -0,0 +1,6 @@
repo: $DRONE_REPO_NAME
status: $DRONE_BUILD_STATUS
commit: $DRONE_COMMIT
message: $DRONE_COMMIT_MESSAGE
build link: $DRONE_BUILD_LINK
repo link: $DRONE_REPO_LINK