#include "WeChatContext.h" #include "../ServiceManager.h" #include "BoostLog.h" #include "WeChatSession.h" #include #include #include #include #include #include #include #include #include #include #include #include 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("xml.ToUserName"); if (!ToUserName) { LOG(error) << "request dont contain ToUserName."; return oss.str(); } auto FromUserName = ptree.get("xml.FromUserName"); auto CreateTime = ptree.get("xml.CreateTime"); auto MsgType = ptree.get("xml.MsgType"); auto content = ptree.get("xml.Content"); auto MsgId = ptree.get("xml.MsgId"); std::shared_ptr session; if (m_sessions.count(FromUserName) > 0) { session = m_sessions.at(FromUserName); } else { session = std::make_shared(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(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); }); }