diff --git a/README.md b/README.md index 5f3771efb04786f2814eac1d3faae4562264b734..734e6b6f4c9596b60d3d495f56aa340681e04d2a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ BIT ICQ, a Realtime Communicating Solution using C++ Qt framework ### 数据格式协议 #### User Model 的 Json 表示 - '''json + ``` json { "MsgType": "UserData, "Username": "...", @@ -20,12 +20,12 @@ BIT ICQ, a Realtime Communicating Solution using C++ Qt framework "..." : "...", } } - ''' + ``` #### 会话及会话关系 1. 好友会话 - '''json + ``` json { "MsgType": "SessionData", "SessionID": 111, @@ -38,10 +38,10 @@ BIT ICQ, a Realtime Communicating Solution using C++ Qt framework "CreatedTime": "yyyy-mm-dd", ] } - ''' + ``` 2. 群组会话 - '''json + ``` json { "MsgType": "SessionData", "SessionID": 112, @@ -56,20 +56,20 @@ BIT ICQ, a Realtime Communicating Solution using C++ Qt framework "CreatedTime": "yyyy-mm-dd", ] } - ''' + ``` 3. 用户处于某一会话 - '''json - { - "MsgType": "UserInSessionRelationship", - "Username": "...", - "SessionID": 111, - } - ''' -#### 消息模型 +``` json +{ + "MsgType": "UserInSessionRelationship", + "Username": "...", + "SessionID": 111, +} +``` - ''' json +#### 消息模型 +``` json { "MsgType": "SessionMessage", "SessionID": 111, @@ -83,7 +83,7 @@ BIT ICQ, a Realtime Communicating Solution using C++ Qt framework } } } - ''' +``` #### 软件架构 软件架构说明 diff --git a/Server/Server.pro b/Server/Server.pro index 607ed281734be46c9ddab3d8b64ab5b12f692d9c..1cafd87247a0dcf0cc1d712c080ebc9308625eb9 100644 --- a/Server/Server.pro +++ b/Server/Server.pro @@ -21,6 +21,7 @@ SOURCES += \ Session/onlinesession.cpp \ main.cpp \ messagemodel.cpp \ + serverdatacenter.cpp \ servermainwindow.cpp \ tcp_server.cpp \ testcases.cpp \ @@ -32,6 +33,7 @@ HEADERS += \ Session/onlinesession.h \ ltest.h \ messagemodel.h \ + serverdatacenter.h \ servermainwindow.h \ tcp_server.h \ usermodel.h diff --git a/Server/Session/onlinesession.cpp b/Server/Session/onlinesession.cpp index cf13c00694dda88a5bfb794c45ff37cc36527441..7c60ba33c204c95eab11df143c3367fe41c4bd5c 100644 --- a/Server/Session/onlinesession.cpp +++ b/Server/Session/onlinesession.cpp @@ -1,4 +1,5 @@ #include "onlinesession.h" +#include OnlineSession::OnlineSession(QJsonObject &json, AbstractSession * parent) : QObject(parent) @@ -10,6 +11,7 @@ void OnlineSession::loadDataFromJson(const QJsonObject &json) { id = json["SessionID"].toInt(); loadUsersFromJson(json); + qDebug() << "debug" ; Profile = json["Profile"].toObject(); if ((getSessionType() == SessionType::GROUP && json["SessionType"] == "GROUP") || (getSessionType() == SessionType::FRIEND && json["SessionType"] == "FRIEND") ) @@ -21,7 +23,10 @@ void OnlineSession::loadDataFromJson(const QJsonObject &json) SessionName = "foo"; } } - else throw "load error"; + else { + qDebug() << "load error" ; + throw "load error"; + } } const QString& OnlineSession::getSessionName() const { @@ -33,10 +38,11 @@ const QString& OnlineSession::getSessionName() const { void OnlineSession::loadUsersFromJson(const QJsonObject& json) { QJsonArray userlist = json["Members"].toArray(); - if (userlist.size() < 2) throw "Value Error"; + + if (userlist.size() < 2) { qDebug() << "Value Error"; throw "userlist 至少需要两个 user 元素"; } for (int i = 0; i < userlist.size(); i++) { QString usrname = userlist.at(i).toObject()["Username"].toString(); members.append(usrname); } - + qDebug() << "Load " << userlist.size() << " users from list"; } diff --git a/Server/Session/onlinesession.h b/Server/Session/onlinesession.h index b4f9b7fdd58328f55f44060a7a3862b698b3db07..13fa2bf24d860b8558f9ed715995acd1e7ca60f1 100644 --- a/Server/Session/onlinesession.h +++ b/Server/Session/onlinesession.h @@ -14,6 +14,7 @@ public: int getSessionID() const { return id; } const QString& getSessionName() const; const QJsonObject& getProfile() const { return Profile; } + QJsonObject generateJsonFromData() const; private: int id; diff --git a/Server/ltest.h b/Server/ltest.h index 4ce89151c8fb8432a8b40cb6d02fb8f3696deb92..8dc295ee7b09af3154cde4338625f085caae4188 100644 --- a/Server/ltest.h +++ b/Server/ltest.h @@ -1,126 +1,165 @@ -#ifndef __LTest_H__ -#define __LTest_H__ - -#include -#include -#include -#include - -class LTestCase; -std::vector ltest_cases; - -#define TESTSUITE(name) \ -class name : public LTestCase { \ -public: \ - name() { \ - ltest_cases.push_back(this); \ - suitename = #name; \ - } \ - ~ name() {\ - dtor(); \ - } - - -#define ENDSUITE(name) \ -} caseEntity##name; - -#define CurPos "(" << __FILE__ << ":" << __LINE__ << ")" - - -#define ERRMSG(msg)\ - std::cout << "#... Error " << CurPos << ": " << msg << "." << std::endl; \ - running_result = -1; \ - return; - -#define ENV( statements ) \ - try {\ - statements \ - } catch(std::exception & e) {\ - ERRMSG(e.what()) \ - } catch(std::string & e) {\ - ERRMSG(e) \ - } catch(const char * e) {\ - ERRMSG(e) \ - } catch(int e) {\ - ERRMSG("ErrCode = " << e) \ - } catch(...) {\ - ERRMSG("Unknown Error") \ - } - -#define CASE(name) void SUITE_##name() - -#define EXE(testname) \ - std::cout << "# Running: case=" << #testname << "... " << std::endl; \ - running_result = 0; \ - SUITE_##testname(); \ - test_pass += running_result == 0; \ - test_num++; \ - test_error += running_result == -1; - -#define assertTrue(cond) \ -ENV(\ - if (!(cond)) {\ - std::cout << "#... Assertion Failed" << CurPos << " : suppose " << #cond << " == true, but got false instead." << std::endl; \ - running_result = 1; \ - return; \ - }) - -#define assertEqual(a, b) \ -ENV(\ - if ((a) != (b)) {\ - std::cout << "#... Assertion Failed" << CurPos <<": " \ - << #a << " != " << #b << std::endl; \ - running_result = 1;\ - return;\ - } \ -) - -#define testlog(msgStr, args...) \ - std::cout << "#... LOG " << CurPos << " ";\ - printf(msgStr "\n", args) - -class LTestCase { -public: - virtual void execute() = 0; - - virtual void ctor() {} - virtual void dtor() {} - - void operator() (bool simplify_output = false) { - test_num = test_pass = test_error = 0; - - - std::cout << "\nSuite: " << suitename << std::endl; - if (!simplify_output) { - std::cout << "##################" << std::endl; - } - - ctor(); - execute(); - if (!simplify_output) - std::cout << "##################" << std::endl; - - if (test_num == test_pass) { - std::cout << test_pass << " Tests Passed In Total" << std::endl; - std::cout << "Test Completed! " << std::endl; - } - else { - std::cout << "Pass " << test_pass << ", Fail " << test_num - test_error - test_pass; - std::cout << ", Error " << test_error << std::endl; - } - } - -protected: - int test_num, test_pass, test_error; - int running_result; - std::string suitename; -}; - -int RunAllTests() { - for (auto c : ltest_cases) { - (*c) (); - } - return 0; -} - -#endif +#ifndef __LTest_H__ +#define __LTest_H__ + +#include +#include +#include +#include + +class LTestCase; + +struct TestSuiteResult { + int test_num, test_error, test_fail, test_pass; + TestSuiteResult& operator += (TestSuiteResult & other) { + test_num += other.test_num, test_fail += other.test_fail; + test_error += other.test_error, test_pass += other.test_pass; + return *this; + } +}; + +std::vector ltest_cases; + +#define TESTSUITE(name) \ +class name : public LTestCase { \ +public: \ + name() { \ + ltest_cases.push_back(this); \ + suitename = #name; \ + } \ + ~ name() {\ + dtor(); \ + } + + +#define ENDSUITE(name) \ +} caseEntity##name; + +#define CurPos "(" << __FILE__ << ":" << __LINE__ << ")" + + +#define ERRMSG(msg)\ + std::cout << "#... Error " << CurPos << ": " << msg << "." << std::endl; \ + running_result = -1; \ + return; + +#define ENV( statements ) \ + try {\ + statements \ + } catch(std::exception & e) {\ + ERRMSG(e.what()) \ + } catch(std::string & e) {\ + ERRMSG(e) \ + } catch(const char * e) {\ + ERRMSG(e) \ + } catch(int e) {\ + ERRMSG("ErrCode = " << e) \ + } catch(...) {\ + ERRMSG("Unknown Error") \ + } + +#define CASE(name) void SUITE_##name() + +#define EXE(testname) \ + std::cout << "# Running: case=" << #testname << "... " << std::endl; \ + running_result = 0; \ + SUITE_##testname(); \ + test_pass += running_result == 0; \ + test_num++; \ + test_error += running_result == -1; + +#define assertTrue(cond) \ +ENV(\ + if (!(cond)) {\ + std::cout << "#... Assertion Failed" << CurPos << " : suppose " << #cond << " == true, but got false instead." << std::endl; \ + running_result = 1; \ + return; \ + }) + +#define assertEqual(a, b) \ +ENV(\ + if ((a) != (b)) {\ + std::cout << "#... Assertion Failed" << CurPos <<": " \ + << #a << " != " << #b << std::endl; \ + running_result = 1;\ + return;\ + } \ +) + +#define assertNoException(statement) \ +ENV( \ + statement; \ + running_result = 0; \ +) + +#define assertException(statement) \ +ENV( \ + running_result = 1; \ + try { \ + statement; \ + } catch (...) { \ + running_result = 0; \ + } \ + if (running_result) { \ + std::cout << "Assertion Failed" << CurPos << ": expect exception, but not" << std::endl; \ + } \ +) + +#define testlog(msgStr, ...) \ + std::cout << "#... LOG " << CurPos << " ";\ + printf(msgStr "\n", ##__VA_ARGS__) + +class LTestCase { +public: + virtual void execute() = 0; + + virtual void ctor() {} + virtual void dtor() {} + + TestSuiteResult operator() (bool simplify_output = true) { + test_num = test_pass = test_error = 0; + + + std::cout << "\nSuite: " << suitename << std::endl; + if (!simplify_output) { + std::cout << "##################" << std::endl; + } + + ctor(); + execute(); + if (!simplify_output) + std::cout << "##################" << std::endl; + + if (test_num == test_pass) { + std::cout << test_pass << " Tests Passed In Total" << std::endl; + } + else { + std::cout << "Pass " << test_pass << ", Fail " << test_num - test_error - test_pass; + std::cout << ", Error " << test_error << std::endl; + } + return { test_num, test_error, test_num - test_pass - test_error, test_pass }; + } + +protected: + int test_num, test_pass, test_error; + int running_result; + std::string suitename; +}; + +int RunAllTests() { + TestSuiteResult sum {0, 0, 0, 0}; + for (auto c : ltest_cases) { + TestSuiteResult result = (*c) (); + sum += result; + } + + std::cout << std::endl << "## Run All Test: Test Result ##" << std::endl; + std::cout << "Pass = " << sum.test_pass << ", Fail = " << sum.test_fail; + std::cout << ", Error = " << sum.test_error << std::endl; + std::cout << std::endl << sum.test_num << " test cases (" << ltest_cases.size(); + std::cout << " suites) executed in total." << std::endl; + std::cout << "Test Completed !" << std::endl; + + return 0; +} + +#endif diff --git a/Server/main.cpp b/Server/main.cpp index 86932884540e96a196edcf0659d4fa8c18acae6b..0012a826db663d9b2cf73852d2db01658691ad3e 100644 --- a/Server/main.cpp +++ b/Server/main.cpp @@ -13,6 +13,7 @@ int main(int argc, char *argv[]) #ifdef TESTMODE return RunAllTests(); #endif + ios::sync_with_stdio(true); QApplication a(argc, argv); ServerMainWindow w; w.show(); diff --git a/Server/messagemodel.cpp b/Server/messagemodel.cpp index 886898422ab80b50bf4c4b62cb2dc2ffb97f6af5..535ff469d145874fe2848aaf569da8a8d1a6e679 100644 --- a/Server/messagemodel.cpp +++ b/Server/messagemodel.cpp @@ -1,17 +1,29 @@ #include "messagemodel.h" - -MessageModel::MessageModel(OnlineUserModel& senderUser, OnlineSession& sessionDest, QObject *parent) : +#include +MessageModel::MessageModel(OnlineUserModel& senderUser, OnlineSession& sessionDest, + QString msgText, QJsonObject msgProfile, QObject *parent) : QObject(parent), - sender(senderUser), session(sessionDest) + sender(&senderUser), session(sessionDest) { + if (!ServerDataCenter::Singleton().isRegistered(sender)) { + qDebug() << "not registered" ; + throw "not registered"; + } senderUsername = senderUser.getUsername(); sessionID = session.getSessionID(); + text = msgText; + profile = msgProfile; } int MessageModel::getSessionID() const { return sessionID; } const QString& MessageModel::getSenderUsername() const { return senderUsername; } const OnlineSession& MessageModel::getSession() const { return session; } -const OnlineUserModel& MessageModel::getSender() const { return sender; } +const OnlineUserModel& MessageModel::getSender() const { return *sender; } const QString& MessageModel::getMessageText() const { return text; } const QJsonObject& MessageModel::getMessageProfile() const { return profile; } +//OnlineMessage::OnlineMessage(QJsonObject json, MessageModel * parent = nullptr) : +//{ + +//} + diff --git a/Server/messagemodel.h b/Server/messagemodel.h index 43ce89e91411bd02f2cbccb3fc2a5d05b0298454..a66770340168646143eacb622c97120dfc210799 100644 --- a/Server/messagemodel.h +++ b/Server/messagemodel.h @@ -2,15 +2,14 @@ #define MESSAGEMODEL_H #include -#include "Session/abstractsession.h" -#include "Session/onlinesession.h" -#include "usermodel.h" +#include "serverdatacenter.h" class MessageModel : public QObject { Q_OBJECT public: - explicit MessageModel(OnlineUserModel& senderUser, OnlineSession& sessionDest, QObject *parent = nullptr); + explicit MessageModel(OnlineUserModel& senderUser, OnlineSession& sessionDest, + QString msgText, QJsonObject msgProfile, QObject *parent = nullptr); int getSessionID() const; const OnlineSession & getSession() const; const QString& getSenderUsername() const; @@ -30,7 +29,7 @@ signals: protected: QString senderUsername; - const OnlineUserModel& sender; + OnlineUserModel* sender; int sessionID; const OnlineSession& session; QString text; @@ -38,11 +37,11 @@ protected: }; -class OnlineMessage : virtual public MessageModel +class OnlineMessage : public MessageModel { Q_OBJECT public: - explicit OnlineMessage(QObject * parent = nullptr); + explicit OnlineMessage(QJsonObject json, MessageModel * parent = nullptr); }; diff --git a/Server/serverdatacenter.cpp b/Server/serverdatacenter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c309edf3a8cf9ce3af453acc132d1f6db64cd9ba --- /dev/null +++ b/Server/serverdatacenter.cpp @@ -0,0 +1,20 @@ +#include "serverdatacenter.h" + +ServerDataCenter::ServerDataCenter(QObject *parent) : QObject(parent) +{ + +} + +void ServerDataCenter::registerUser(OnlineUserModel * newuser) { + if (users.contains(newuser->getUsername())) { + throw "multiple username"; + } + users[newuser->getUsername()] = newuser; + newuser->setParent(this); + registeredObjects.insert(newuser); +} + +OnlineUserModel& ServerDataCenter::getUser(QString username) { + if (!users.contains(username)) throw "username not found"; + return *users[username]; +} diff --git a/Server/serverdatacenter.h b/Server/serverdatacenter.h new file mode 100644 index 0000000000000000000000000000000000000000..2eb87d590de1558afd0f32ad71258be576989d96 --- /dev/null +++ b/Server/serverdatacenter.h @@ -0,0 +1,37 @@ +#ifndef SERVERDATACENTER_H +#define SERVERDATACENTER_H + +#include +#include +#include + +#include "Session/abstractsession.h" +#include "Session/onlinesession.h" +#include "Session/offlinesession.h" + +#include "usermodel.h" +#include "messagemodel.h" + +class ServerDataCenter : public QObject +{ + Q_OBJECT +public: + static ServerDataCenter& Singleton(QObject * parent = nullptr) { + static ServerDataCenter * singleton = new ServerDataCenter(parent); + return * singleton; + } + + void registerUser(OnlineUserModel * newuser); + OnlineUserModel & getUser(QString username); + bool isRegistered(QObject* obj) { return registeredObjects.contains(obj); } + + +signals: +private: + explicit ServerDataCenter(QObject *parent = nullptr); + QMap users; + QSet registeredObjects; +}; + + +#endif // SERVERDATACENTER_H diff --git a/Server/testcases.cpp b/Server/testcases.cpp index cceebf04c24d0ea6181ece40022374a9d8e6a8be..94cc7d66d8fbc1d650ffc71a8c79a047496e8bad 100644 --- a/Server/testcases.cpp +++ b/Server/testcases.cpp @@ -3,9 +3,7 @@ #include #include "ltest.h" -#include "Session/abstractsession.h" -#include "Session/offlinesession.h" -#include "Session/onlinesession.h" +#include "serverdatacenter.h" using namespace std; @@ -15,8 +13,17 @@ CASE(BasicOperations) assertTrue(1 + 1 == 2); } +void raiseError() { + throw "A Error"; +} + +CASE(AssertExceptionWork) { + assertException( raiseError(); ); +} + void execute() { EXE(BasicOperations); + EXE(AssertExceptionWork); } ENDSUITE(BasicTest) @@ -56,6 +63,7 @@ CASE(CreatingLocalSessionWithJsonObject) { user["Username"] = "UserB"; userlist.append(user); json["Members"] = userlist; + assertEqual(json["Members"].toArray().size(), 2); OnlineSession session(json); assertEqual(session.getSessionID(), 101); @@ -107,22 +115,90 @@ void execute() { ENDSUITE(UserTest) +TESTSUITE(DataCenterTest) + +CASE(CanGetSingleton) { + assertNoException( + ServerDataCenter::Singleton(); + ); +} + +CASE(getOnlineUserFromUsername) { + OfflineUserModel user("userA", &obj); + user.setNickname("nicknameA"); + user.setSigniture("None"); + auto json = user.generateUserModelJson(); + OnlineUserModel newuser(json, &obj); + + OfflineUserModel userB("userB", &obj); + userB.setNickname("nicknameA"); + userB.setSigniture("None"); + json = userB.generateUserModelJson(); + OnlineUserModel newuserB(json, &obj); + + auto& dcenter = ServerDataCenter::Singleton(); + dcenter.registerUser(& newuser); + dcenter.registerUser(& newuserB); + + assertEqual(dcenter.getUser("userA").getNickname(), "nicknameA"); + assertEqual(dcenter.getUser("userB").getSigniture(), "None"); + + assertEqual(dcenter.getUser("userA").parent(), &dcenter); + assertEqual(dcenter.getUser("userB").parent(), &dcenter); +} + +void execute() { + EXE(CanGetSingleton); +} + +ENDSUITE(DataCenterTest) + + TESTSUITE(MessageTest) -CASE(NewMessageFromOnlineUserAndSession) +CASE(NewMessage_GeneratedBy_UserAndSessionObject) { OfflineUserModel userA("userA", &obj); userA.setNickname("nicknameA"); userA.setSigniture("None"); auto json = userA.generateUserModelJson(); + OnlineUserModel userA_online(json, &obj); + OfflineUserModel userB("userB", &obj); - userB.setNickname("nicknameB"); - userB.setSigniture("None Either"); + userB.setNickname("nicknameA"); + userB.setSigniture("None"); json = userB.generateUserModelJson(); + OnlineUserModel userB_online(json, &obj); + + ServerDataCenter & dcenter = ServerDataCenter::Singleton(); + dcenter.registerUser(&userA_online); + dcenter.registerUser(&userB_online); + + QJsonObject user; + QJsonArray userlist; + json["MsgType"] = "SessionData"; + json["SessionID"] = 101; + json["SessionType"] = "FRIEND"; + + user["Username"] = "UserA"; + userlist.append(user); + user["Username"] = "UserB"; + userlist.append(user); + json["Members"] = userlist; + assertEqual(json["Members"].toArray().size(), 2); + + OnlineSession session(json); + + testlog("Constructed mock users and session."); + + MessageModel msg(userA_online, session, "a->b text", QJsonObject(), &obj); + testlog("Generated offline message"); + assertEqual(msg.getType(), MessageModel::Type::Offline); + assertEqual(msg.getMessageText(), "a->b text"); } void execute() { - EXE(NewMessageFromOnlineUserAndSession); + EXE(NewMessage_GeneratedBy_UserAndSessionObject); } ENDSUITE(MessageTest) diff --git a/Server/unittest/testcases.cpp b/Server/unittest/testcases.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Server/usermodel.cpp b/Server/usermodel.cpp index 45a0c91ec9234435541f6a1bbd75a38e1d01f6a0..7555edc47dbdc7207e541f1d27abcbedd2f1c361 100644 --- a/Server/usermodel.cpp +++ b/Server/usermodel.cpp @@ -22,6 +22,12 @@ void OnlineUserModel::loadBasicInfoFromJson(QJsonObject &json) { profile = json["Profile"].toObject(); } +OnlineUserModel::OnlineUserModel(OnlineUserModel& old) { + username = old.username; + nickname = old.nickname; + profile = old.profile; +} + QJsonObject OfflineUserModel::generateUserModelJson() const { QJsonObject json; diff --git a/Server/usermodel.h b/Server/usermodel.h index 85fcabbc3eff22838972015e378cd687f57f1109..902b5cff0497031799c83600d624ee06cb92a6b8 100644 --- a/Server/usermodel.h +++ b/Server/usermodel.h @@ -45,6 +45,7 @@ class OnlineUserModel : virtual public UserModel, virtual public QObject Q_OBJECT public: OnlineUserModel(QJsonObject &json, QObject *parent = nullptr); + OnlineUserModel(OnlineUserModel& old); Type getType() const { return Type::Online; } const QString& getNickname() const { return nickname; } const QString getSigniture() const { return profile["Signiture"].toString(); }