2024-01-24 23:19:53 +08:00
# include "Application.h"
2024-11-26 22:58:54 +08:00
# include "Database/Session.h"
2024-01-24 23:19:53 +08:00
# include "DateTime.h"
# include "HttpSession.h"
# include "IoContext.h"
2024-11-10 18:33:39 +08:00
# include "Nng/SocketAisoWrapper.h"
2024-01-24 23:19:53 +08:00
# include "ServiceLogic.h"
# include "ServiceManager.h"
2024-05-03 22:30:46 +08:00
# include "SystemUsage.h"
2024-01-24 23:19:53 +08:00
# include "WeChatContext/CorporationContext.h"
2024-11-26 22:58:54 +08:00
# include <Wt/Dbo/Json.h>
2024-11-10 18:33:39 +08:00
# include <boost/json/object.hpp>
# include <boost/json/serialize.hpp>
2024-05-05 22:00:15 +08:00
# include <boost/stacktrace.hpp>
2024-01-24 23:19:53 +08:00
2024-11-10 18:33:39 +08:00
constexpr auto IpcUrl = " ipc:///tmp/nng_ipc_server " ;
2024-11-27 20:23:02 +08:00
static std : : vector < std : : string > urlFilter = {
" / " ,
" /search " ,
" /LoginPage " ,
" /MessageBoard " ,
" /我的笔记 " ,
" /我的笔记/ " ,
" /我的博客 " ,
} ;
2024-01-24 23:19:53 +08:00
Application : : Application ( const std : : string & path )
: ApplicationSettings ( path ) , m_router { std : : make_shared < boost : : urls : : router < RequestHandler > > ( ) } {
// clang-format off
m_router - > insert ( " /{path*} " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
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 ( getDocumentRoot ( ) , target ) ;
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 . content_length ( size ) ;
res . keep_alive ( request . keep_alive ( ) ) ;
session . reply ( std : : move ( res ) ) ;
} ) ;
m_router - > insert ( " /wechat/{session*} " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
ServiceLogic : : onWechat ( shared_from_this ( ) , request ,
[ & session ] ( auto & & response ) { session . reply ( std : : move ( response ) ) ; } ) ;
} ) ;
m_router - > insert ( " /api/v1/tasklist " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
using namespace boost : : beast ;
2024-11-27 19:23:06 +08:00
auto database = Database : : session ( ) ;
2024-11-26 22:58:54 +08:00
Tasks tasks = database - > find < Task > ( ) ;
std : : ostringstream oss ;
Wt : : Dbo : : jsonSerialize ( tasks , oss ) ;
2024-01-24 23:19:53 +08:00
http : : response < boost : : beast : : http : : string_body > s { boost : : beast : : http : : status : : ok , request . version ( ) } ;
s . set ( http : : field : : server , BOOST_BEAST_VERSION_STRING ) ;
s . set ( http : : field : : content_type , " application/json;charset=UTF-8 " ) ;
s . keep_alive ( request . keep_alive ( ) ) ;
2024-11-26 22:58:54 +08:00
s . body ( ) = oss . str ( ) ;
2024-01-24 23:19:53 +08:00
s . prepare_payload ( ) ;
session . reply ( std : : move ( s ) ) ;
} ) ;
2024-11-26 22:58:54 +08:00
m_router - > insert ( " /api/v1/task/add " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) mutable {
2024-01-24 23:19:53 +08:00
using namespace boost : : beast ;
2024-11-27 00:08:24 +08:00
using namespace std : : chrono ;
2024-01-24 23:19:53 +08:00
LOG ( info ) < < " add task: " < < request . body ( ) ;
auto rootJson = boost : : json : : parse ( request . body ( ) ) ;
auto & root = rootJson . as_object ( ) ;
std : : string content ;
if ( root . contains ( " content " ) ) {
content = root . at ( " content " ) . as_string ( ) ;
}
2024-11-27 19:23:06 +08:00
auto database = Database : : session ( ) ; ;
2024-11-26 22:58:54 +08:00
auto task = std : : make_unique < Task > ( ) ;
2024-11-27 00:08:24 +08:00
task - > createTime = system_clock : : time_point ( seconds ( root . at ( " createTime " ) . as_int64 ( ) ) ) ;
2024-11-26 22:58:54 +08:00
task - > content = content ;
task - > comment = std : : string ( root . at ( " comment " ) . as_string ( ) ) ;
auto t = database - > add ( std : : move ( task ) ) ;
Wt : : Dbo : : ptr < Task > parent = database - > find < Task > ( " where id=? " ) . bind ( root . at ( " parentId " ) . as_int64 ( ) ) ;
if ( parent ) {
parent . modify ( ) - > children . insert ( t ) ;
}
2024-01-24 23:19:53 +08:00
boost : : json : : object reply ;
2024-11-26 22:58:54 +08:00
reply [ " status " ] = 0 ;
2024-01-24 23:19:53 +08:00
http : : response < boost : : beast : : http : : string_body > s { boost : : beast : : http : : status : : ok , request . version ( ) } ;
s . set ( http : : field : : server , BOOST_BEAST_VERSION_STRING ) ;
s . set ( http : : field : : content_type , " application/json;charset=UTF-8 " ) ;
s . keep_alive ( request . keep_alive ( ) ) ;
s . body ( ) = boost : : json : : serialize ( reply ) ;
s . prepare_payload ( ) ;
session . reply ( std : : move ( s ) ) ;
} ) ;
m_router - > insert ( " /api/v1/task/delete/{id} " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
using namespace boost : : beast ;
LOG ( info ) < < " delete task: " < < matches . at ( " id " ) ;
2024-11-27 19:23:06 +08:00
auto database = Database : : session ( ) ; ;
2024-11-26 22:58:54 +08:00
Wt : : Dbo : : ptr < Task > joe = database - > find < Task > ( ) . where ( " id = ? " ) . bind ( std : : stoi ( matches . at ( " id " ) ) ) ;
int status = - 1 ;
if ( joe ) {
joe . remove ( ) ;
status = 0 ;
}
2024-01-24 23:19:53 +08:00
boost : : json : : object reply ;
2024-11-26 22:58:54 +08:00
reply [ " status " ] = status ;
2024-01-24 23:19:53 +08:00
http : : response < boost : : beast : : http : : string_body > s { boost : : beast : : http : : status : : ok , request . version ( ) } ;
s . set ( http : : field : : server , BOOST_BEAST_VERSION_STRING ) ;
s . set ( http : : field : : content_type , " application/json;charset=UTF-8 " ) ;
s . keep_alive ( request . keep_alive ( ) ) ;
s . body ( ) = boost : : json : : serialize ( reply ) ;
s . prepare_payload ( ) ;
session . reply ( std : : move ( s ) ) ;
} ) ;
m_router - > insert ( " /trigger-ci.hook " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & 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 boost : : urls : : matches & 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 " ) ) ;
} ) ;
2024-11-27 19:23:06 +08:00
2024-11-26 22:58:54 +08:00
m_router - > insert ( " /api/v1/visit_analysis " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
2024-07-30 22:17:55 +08:00
using namespace boost : : beast ;
auto rootJson = boost : : json : : parse ( request . body ( ) ) ;
auto & root = rootJson . as_object ( ) ;
std : : string url ;
if ( root . contains ( " url " ) ) {
url = root [ " url " ] . as_string ( ) ;
}
2024-11-27 19:23:06 +08:00
auto database = Database : : session ( ) ;
2024-11-27 23:50:04 +08:00
if ( std : : filesystem : : exists ( " amass_blog " + url ) & & url . find ( " /我的博客/page " ) ! = 0 ) {
if ( url . size ( ) > 1 & & url . back ( ) = = ' / ' ) {
url . pop_back ( ) ;
}
2024-11-27 19:23:06 +08:00
Wt : : Dbo : : Transaction transaction ( * database ) ;
auto record = std : : make_unique < VisitorRecord > ( ) ;
record - > time = std : : chrono : : system_clock : : now ( ) ;
record - > url = url ;
2024-08-06 22:24:26 +08:00
if ( root . contains ( " visitor_uuid " ) ) {
2024-11-27 19:23:06 +08:00
record - > visitorUuid = root [ " visitor_uuid " ] . as_string ( ) ;
2024-08-06 22:24:26 +08:00
}
std : : string userAgent ;
if ( root . contains ( " user_agent " ) ) {
2024-11-27 19:23:06 +08:00
record - > userAgent = root [ " user_agent " ] . as_string ( ) ;
2024-08-06 22:24:26 +08:00
}
2024-11-27 19:23:06 +08:00
database - > add ( std : : move ( record ) ) ;
2024-08-06 22:24:26 +08:00
}
2024-11-27 20:00:57 +08:00
Wt : : Dbo : : Transaction transaction ( * database ) ;
2024-07-30 22:17:55 +08:00
boost : : json : : object reply ;
2024-11-27 19:23:06 +08:00
reply [ " page_view_count " ] = database - > query < int > ( " SELECT COUNT(*) FROM visitor_record WHERE url = ? " ) . bind ( std : : string ( url ) ) ;
reply [ " unique_visitor_count " ] = database - > query < int > ( " SELECT COUNT(DISTINCT visitor_uuid) FROM visitor_record WHERE url = ? " ) . bind ( std : : string ( url ) ) ;
reply [ " site_page_view_count " ] = database - > query < int > ( " SELECT COUNT(*) FROM visitor_record " ) ;
reply [ " site_unique_visitor_count " ] = database - > query < int > ( " SELECT COUNT(DISTINCT visitor_uuid) FROM visitor_record " ) ;
2024-07-30 23:17:42 +08:00
2024-07-30 22:17:55 +08:00
http : : response < boost : : beast : : http : : string_body > s { boost : : beast : : http : : status : : ok , request . version ( ) } ;
s . set ( http : : field : : server , BOOST_BEAST_VERSION_STRING ) ;
s . set ( http : : field : : content_type , " application/json;charset=UTF-8 " ) ;
s . keep_alive ( request . keep_alive ( ) ) ;
s . body ( ) = boost : : json : : serialize ( reply ) ;
s . prepare_payload ( ) ;
session . reply ( std : : move ( s ) ) ;
} ) ;
2024-07-31 22:28:05 +08:00
2024-11-26 22:58:54 +08:00
m_router - > insert ( " /api/v1/most_viewed_urls " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
2024-08-05 23:01:19 +08:00
using namespace boost : : beast ;
int size = 5 ;
std : : error_code error ;
if ( ! request . body ( ) . empty ( ) ) {
auto rootJson = boost : : json : : parse ( request . body ( ) , error ) ;
if ( error ) {
LOG ( info ) < < " < " < < request . body ( ) < < " > parse json error: " < < error . message ( ) ;
} else {
auto & root = rootJson . as_object ( ) ;
if ( root . contains ( " size " ) ) {
size = root . at ( " size " ) . as_int64 ( ) ;
}
}
}
2024-11-27 19:23:06 +08:00
auto database = Database : : session ( ) ;
2024-11-27 20:00:57 +08:00
Wt : : Dbo : : Transaction transaction ( * database ) ;
2024-11-27 20:23:02 +08:00
Wt : : Dbo : : collection < std : : tuple < std : : string , int > > query = database - > query < std : : tuple < std : : string , int > > ( " SELECT url, COUNT(*) as count FROM visitor_record GROUP BY url ORDER BY count DESC LIMIT ? " ) . bind ( static_cast < int > ( size + urlFilter . size ( ) ) ) ;
2024-08-05 23:01:19 +08:00
boost : : json : : array reply ;
2024-11-27 20:23:02 +08:00
int index = 0 ;
2024-11-27 19:23:06 +08:00
for ( auto & [ url , count ] : query ) {
2024-11-27 20:23:02 +08:00
if ( std : : find ( urlFilter . cbegin ( ) , urlFilter . cend ( ) , url ) ! = urlFilter . cend ( ) ) continue ;
2024-08-05 23:01:19 +08:00
boost : : json : : object object ;
2024-11-27 19:23:06 +08:00
object [ " url " ] = url ;
object [ " count " ] = count ;
2024-08-05 23:01:19 +08:00
reply . push_back ( std : : move ( object ) ) ;
2024-11-27 20:23:02 +08:00
index + + ;
if ( index > = size ) break ;
2024-08-05 23:01:19 +08:00
}
http : : response < boost : : beast : : http : : string_body > s { boost : : beast : : http : : status : : ok , request . version ( ) } ;
s . set ( http : : field : : server , BOOST_BEAST_VERSION_STRING ) ;
2024-08-06 09:48:25 +08:00
s . set ( http : : field : : content_type , " application/json;charset=UTF-8 " ) ;
s . keep_alive ( request . keep_alive ( ) ) ;
s . body ( ) = boost : : json : : serialize ( reply ) ;
s . prepare_payload ( ) ;
session . reply ( std : : move ( s ) ) ;
} ) ;
2024-11-26 22:58:54 +08:00
m_router - > insert ( " /api/v1/latest_viewed_urls " , [ this ] ( HttpSession & session , const Request & request , const boost : : urls : : matches & matches ) {
2024-08-06 09:48:25 +08:00
using namespace boost : : beast ;
2024-11-27 19:23:06 +08:00
using namespace std : : chrono ;
2024-08-06 09:48:25 +08:00
int size = 5 ;
std : : error_code error ;
if ( ! request . body ( ) . empty ( ) ) {
auto rootJson = boost : : json : : parse ( request . body ( ) , error ) ;
if ( error ) {
LOG ( info ) < < " < " < < request . body ( ) < < " > parse json error: " < < error . message ( ) ;
} else {
auto & root = rootJson . as_object ( ) ;
if ( root . contains ( " size " ) ) {
size = root . at ( " size " ) . as_int64 ( ) ;
}
}
}
2024-11-27 19:23:06 +08:00
auto database = Database : : session ( ) ;
2024-11-27 20:00:57 +08:00
Wt : : Dbo : : Transaction transaction ( * database ) ;
2024-11-27 19:23:06 +08:00
using Reslut = std : : tuple < std : : string , system_clock : : time_point > ;
2024-11-27 20:23:02 +08:00
Wt : : Dbo : : collection < Reslut > query = database - > query < Reslut > ( " SELECT url, MAX(time) FROM visitor_record GROUP BY url ORDER BY MAX(time) DESC LIMIT ? " ) . bind ( static_cast < int > ( size + urlFilter . size ( ) ) ) ;
2024-08-06 09:48:25 +08:00
boost : : json : : array reply ;
2024-11-27 20:23:02 +08:00
int index = 0 ;
2024-11-27 19:23:06 +08:00
for ( auto & [ url , time ] : query ) {
2024-11-27 20:23:02 +08:00
if ( std : : find ( urlFilter . cbegin ( ) , urlFilter . cend ( ) , url ) ! = urlFilter . cend ( ) ) continue ;
2024-08-06 09:48:25 +08:00
boost : : json : : object object ;
2024-11-27 19:23:06 +08:00
object [ " url " ] = url ;
object [ " time " ] = duration_cast < seconds > ( time . time_since_epoch ( ) ) . count ( ) ;
2024-08-06 09:48:25 +08:00
reply . push_back ( std : : move ( object ) ) ;
2024-11-27 20:23:02 +08:00
index + + ;
if ( index > = size ) break ;
2024-08-06 09:48:25 +08:00
}
http : : response < boost : : beast : : http : : string_body > s { boost : : beast : : http : : status : : ok , request . version ( ) } ;
s . set ( http : : field : : server , BOOST_BEAST_VERSION_STRING ) ;
2024-08-05 23:01:19 +08:00
s . set ( http : : field : : content_type , " application/json;charset=UTF-8 " ) ;
s . keep_alive ( request . keep_alive ( ) ) ;
s . body ( ) = boost : : json : : serialize ( reply ) ;
s . prepare_payload ( ) ;
session . reply ( std : : move ( s ) ) ;
} ) ;
2024-11-27 19:23:06 +08:00
// clang-format on
2024-01-24 23:19:53 +08:00
m_ioContext = Amass : : Singleton < IoContext > : : instance < Amass : : Construct > ( getThreads ( ) ) ;
m_timer = std : : make_shared < boost : : asio : : system_timer > ( * m_ioContext - > ioContext ( ) ) ;
2024-11-10 18:33:39 +08:00
m_replyer = std : : make_shared < Nng : : Asio : : Socket > ( * m_ioContext - > ioContext ( ) , Nng : : Reply ) ;
m_replyer - > listen ( IpcUrl ) ;
2024-05-03 22:30:46 +08:00
m_systemUsage = std : : make_shared < SystemUsage > ( * m_ioContext - > ioContext ( ) , getHomeAssistantAccessToken ( ) ) ;
m_systemUsage - > start ( ) ;
2024-01-24 23:19:53 +08:00
alarmTask ( ) ;
}
boost : : asio : : io_context & Application : : ioContext ( ) {
return * m_ioContext - > ioContext ( ) ;
}
const Application : : RequestHandler * Application : : find ( boost : : urls : : segments_encoded_view path ,
boost : : urls : : matches_base & matches ) const noexcept {
2024-05-05 22:00:15 +08:00
const Application : : RequestHandler * ret = nullptr ;
try {
ret = m_router - > find ( path , matches ) ;
} catch ( const std : : exception & e ) {
boost : : stacktrace : : stacktrace trace = boost : : stacktrace : : stacktrace : : from_current_exception ( ) ;
LOG ( error ) < < e . what ( ) < < " , trace: \n " < < trace ;
}
return ret ;
2024-01-24 23:19:53 +08:00
}
2024-10-23 19:53:51 +08:00
void Application : : insertUrl ( std : : string_view url , RequestHandler & & handler ) {
m_router - > insert ( url , std : : move ( handler ) ) ;
}
2024-01-24 23:19:53 +08:00
int Application : : exec ( ) {
2024-11-10 18:33:39 +08:00
startAcceptRequest ( ) ;
2024-01-24 23:19:53 +08:00
LOG ( info ) < < " application start successful ... " ;
startCheckInterval ( * m_ioContext - > ioContext ( ) , 2 ) ;
2024-11-10 18:33:39 +08:00
m_ioContext - > run ( ) ;
2024-01-24 23:19:53 +08:00
LOG ( info ) < < " application exit successful ... " ;
return m_status ;
}
void Application : : alarmTask ( ) {
int hour = 10 ;
int minute = 30 ;
auto alarmTime = DateTime : : currentDateTime ( ) ;
alarmTime . setHour ( hour ) ;
alarmTime . setMinute ( minute ) ;
if ( std : : chrono : : system_clock : : now ( ) > alarmTime ( ) ) {
alarmTime = alarmTime . tomorrow ( ) ;
}
m_timer - > expires_at ( alarmTime ( ) ) ;
m_timer - > async_wait ( [ this ] ( const boost : : system : : error_code & error ) mutable {
if ( error ) {
LOG ( error ) < < error . message ( ) ;
return ;
}
2024-11-27 19:23:06 +08:00
auto session = Database : : session ( ) ;
2024-11-26 22:58:54 +08:00
Tasks tasks = session - > find < Task > ( ) ;
2024-01-24 23:19:53 +08:00
bool founded = false ;
std : : string content ;
for ( auto & task : tasks ) {
if ( founded ) break ;
2024-11-26 22:58:54 +08:00
for ( auto child : task - > children ) {
if ( ! child - > finished ) {
content = child - > content ;
2024-01-24 23:19:53 +08:00
founded = true ;
break ;
}
}
2024-11-26 22:58:54 +08:00
if ( ! founded & & ! task - > finished ) {
content = task - > content ;
2024-01-24 23:19:53 +08:00
founded = true ;
}
}
if ( founded ) {
std : : ostringstream oss ;
oss < < " 待完成事项: " < < std : : endl ;
oss < < " ========== " < < std : : endl ;
oss < < content < < std : : endl ;
oss < < " ========== " < < std : : endl ;
oss < < " 每天都要过得充实开心哦~ " ;
auto manager = Amass : : Singleton < ServiceManager > : : instance ( ) ;
if ( manager ) manager - > sendMessage ( NotifyServerChan , oss . str ( ) ) ;
}
alarmTask ( ) ;
} ) ;
2024-11-10 18:33:39 +08:00
}
void Application : : startAcceptRequest ( ) {
m_replyer - > asyncReceive ( [ ptr { weak_from_this ( ) } ] ( const boost : : system : : error_code & error , const Nng : : Buffer & buffer ) {
if ( error ) {
LOG ( error ) < < error . message ( ) ;
}
LOG ( info ) < < buffer . data ( ) ;
if ( ptr . expired ( ) ) return ;
auto self = ptr . lock ( ) ;
auto value = boost : : json : : parse ( buffer . data ( ) ) ;
auto & request = value . as_object ( ) ;
if ( request . at ( " command " ) . as_string ( ) = = " exit " ) {
boost : : json : : object reply ;
reply [ " status " ] = 0 ;
reply [ " message " ] = " will exit. " ;
auto txt = boost : : json : : serialize ( reply ) ;
self - > m_replyer - > send ( txt . data ( ) , txt . size ( ) ) ;
std : : raise ( SIGUSR1 ) ; // 发送自定义信号
}
self - > startAcceptRequest ( ) ;
} ) ;
}
void Application : : requetExit ( ) {
LOG ( info ) < < " send exit request to program. " ;
Nng : : Socket request ( Nng : : Request ) ;
2024-11-10 20:23:00 +08:00
request . setOption ( Nng : : RecvTimeout , std : : chrono : : milliseconds ( 2000 ) ) ;
std : : error_code error ;
request . dial ( IpcUrl , error ) ;
if ( error ) {
LOG ( error ) < < error . message ( ) ;
return ;
}
2024-11-10 18:33:39 +08:00
boost : : json : : object object ;
object [ " command " ] = " exit " ;
auto text = boost : : json : : serialize ( object ) ;
request . send ( text . data ( ) , text . size ( ) ) ;
auto buffer = request . recv ( ) ;
LOG ( info ) < < buffer . data < char > ( ) ;
2024-01-24 23:19:53 +08:00
}