From 7b3f32f3340c844f4b93d22656dac147a9e96c3d Mon Sep 17 00:00:00 2001 From: lixiaoyuan Date: Fri, 18 Jul 2025 09:08:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=B3=BB=E7=BB=9F=E7=AE=A1?= =?UTF-8?q?=E7=90=86web=E7=AB=AF=E5=8A=9F=E8=83=BD,=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3,=E5=AE=9E=E7=8E=B0=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 35 +++- src/app/Admin.cpp | 18 ++ src/app/Admin.h | 16 ++ src/app/Application.cpp | 64 ++++++- src/app/Application.h | 5 + src/app/Config.cpp | 38 ++++ src/app/Config.h | 26 +++ src/app/Dao.cpp | 28 +-- src/app/Dao.h | 4 +- src/app/Device.cpp | 126 +++++++++++++ src/app/Device.h | 51 +++++- src/app/Operator.cpp | 7 +- src/app/errcode.h | 15 ++ src/cmake_msvc2019.bat | 2 +- src/common/DataFields.cpp | 60 +++---- src/common/DataFields.h | 10 +- src/common/JsonN.h | 67 +++++++ src/common/Snowflake.h | 325 ++++++++++++++++------------------ src/database/DaoEntity.cpp | 24 ++- src/database/DaoEntity.h | 5 +- src/database/MysqlClient.cpp | 7 +- src/database/MysqlClient.h | 6 +- src/main.cpp | 85 +++++---- src/protocol/Communicator.cpp | 28 +++ src/protocol/Communicator.h | 39 ++++ src/protocol/TcpEntity.cpp | 281 +++++++++++++++++++++++++++++ src/protocol/TcpEntity.h | 117 ++++++++++++ src/widgets/MainWindow.cpp | 54 +++--- src/widgets/MainWindow.h | 4 +- src/widgets/WebHandler.cpp | 143 +++++++++++++-- src/widgets/WebHandler.h | 19 +- 31 files changed, 1384 insertions(+), 325 deletions(-) create mode 100644 src/app/Admin.cpp create mode 100644 src/app/Admin.h create mode 100644 src/app/Config.cpp create mode 100644 src/app/Config.h create mode 100644 src/app/errcode.h create mode 100644 src/common/JsonN.h create mode 100644 src/protocol/Communicator.cpp create mode 100644 src/protocol/Communicator.h create mode 100644 src/protocol/TcpEntity.cpp create mode 100644 src/protocol/TcpEntity.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b67967..afa45c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,16 +8,30 @@ set(CMAKE_CXX_STANDARD 17) # 【注意】visual studio编译时的字符编码问题,配置属性 --> C/C++ --> 命令行 --> 其它选项: /utf-8 或添加如下指令 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") -# Qt_PATH 为 Qt 的安装地址 -set(QT_PATH "D:/Programs/Qt5/5.15.2/msvc2019_64") -set(CMAKE_PREFIX_PATH ${QT_PATH}/lib/cmake) +# 设置 Release 模式启用调试信息 +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") +# 设置 Release 模式的优化级别和其他选项 +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Od /Ob2") +# 设置 Release 模式下链接器的调试信息 +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") +# Qt_PATH 为 Qt 的安装地址 +set(QT_PATH "D:/Programs/Qt5/5.15.2/msvc2019") +set(CMAKE_PREFIX_PATH ${QT_PATH}/lib/cmake) # 开启自动编译 set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -find_package(Qt5 COMPONENTS Widgets AxContainer Network SerialBus SerialPort Charts WebEngineWidgets REQUIRED) +find_package(Qt5 COMPONENTS + Widgets + AxContainer + Network + SerialBus + SerialPort + Charts + WebEngineWidgets +REQUIRED) add_definitions(-DWIN32_LEAN_AND_MEAN) @@ -31,6 +45,7 @@ include_directories( ${ROOT_PATH}/widgets ${THIRDPARTY_PATH} ${THIRDPARTY_PATH}/mysql/include + ${THIRDPARTY_PATH}/nlohmann_json-3.11.2 ) macro(ADD_SOURCE_GROUP srcpath) @@ -53,8 +68,16 @@ ADD_SOURCE_GROUP(widgets/pages) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin) add_executable(${PROJECT_NAME} ${SOURCE_FILE}) -target_link_libraries(${PROJECT_NAME} Qt5::Widgets Qt5::AxContainer Qt5::Network Qt5::SerialBus Qt5::SerialPort Qt5::Charts Qt5::WebEngineWidgets) +target_link_libraries(${PROJECT_NAME} + Qt5::Widgets + Qt5::AxContainer + Qt5::Network + Qt5::SerialBus + Qt5::SerialPort + Qt5::Charts + Qt5::WebEngineWidgets +) target_link_libraries(${PROJECT_NAME} ws2_32 iphlpapi - ${THIRDPARTY_PATH}/mysql/lib/x64/libmysql.lib + ${THIRDPARTY_PATH}/mysql/lib/Win32/libmysql.lib ) \ No newline at end of file diff --git a/src/app/Admin.cpp b/src/app/Admin.cpp new file mode 100644 index 0000000..2e4d511 --- /dev/null +++ b/src/app/Admin.cpp @@ -0,0 +1,18 @@ +#include "Admin.h" +#include "app/Dao.h" +#include "common/Logger.h" + +Errcode Admin::longin(std::string username, std::string passwd) +{ + std::string err; + Errcode ecode = DAO::login(nullptr, username, passwd, err); + if (ecode != Errcode::OK) + { + XLOGE() << "login error, username=" << username << ", err=" << err; + } + else + { + XLOGE() << "login success, username=" << username; + } + return ecode; +} \ No newline at end of file diff --git a/src/app/Admin.h b/src/app/Admin.h new file mode 100644 index 0000000..ed46e63 --- /dev/null +++ b/src/app/Admin.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "app/errcode.h" + +class Admin +{ +public: + static Admin& instance() + { + static Admin admin; + return admin; + } + + Errcode longin(std::string username, std::string pwd); +}; \ No newline at end of file diff --git a/src/app/Application.cpp b/src/app/Application.cpp index 9608e01..923743f 100644 --- a/src/app/Application.cpp +++ b/src/app/Application.cpp @@ -2,20 +2,68 @@ #include "common/Utils.h" +#include "Config.h" +#include "app/Dao.h" +#include "app/Device.h" + void Application::init() { - std::thread([=]() - { - while (!isQuit()) { runThreadMain(); } - }).detach(); + Config::init("assets/config/app.json"); + + // 设置数据库配置 + DaoEntity::setOption(Config::option.database.host, + Config::option.database.port, + Config::option.database.user, + Config::option.database.passwd, + Config::option.database.dbname); + XLOGI() << "[APP] set database option: host=" << Config::option.database.host + << ", port=" << Config::option.database.port + << ", user=" << Config::option.database.user + << ", dbname=" << Config::option.database.dbname; + + // 连接数据库,读取基础信息 + + + // 读取设备信息,连接设备 + this->initDevice(); + std::thread([=]() { runThreadDevice(); }).detach(); + + // 创建主业务循环线程 + std::thread([=]() { runThreadMain(); }).detach(); +} + +void Application::initDevice() +{ + DaoEntity dao(""); + std::string sql = "select * from device;"; + vector result; + dao.exec(sql, result); + for (auto& fields: result) + { + Device::add(fields); + } } void Application::runThreadMain() { - static TimeTick tt; - tt.elapse(1000); + while (!isQuit()) + { - //XLOGD() << "HelloWorld"; - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + + + +void Application::runThreadDevice() +{ + while (!isQuit()) + { + + + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } } \ No newline at end of file diff --git a/src/app/Application.h b/src/app/Application.h index 6adc949..3aff44a 100644 --- a/src/app/Application.h +++ b/src/app/Application.h @@ -15,11 +15,16 @@ public: } void init(); + void initDevice(); + bool isQuit() { return isQuit_; } Operator& getOperator() { return op_; } void runThreadMain(); + + void runThreadDevice(); + private: bool isQuit_ = false; diff --git a/src/app/Config.cpp b/src/app/Config.cpp new file mode 100644 index 0000000..df2cd9b --- /dev/null +++ b/src/app/Config.cpp @@ -0,0 +1,38 @@ +#include "Config.h" + +#include + +#include "common/JsonN.h" +#include "Logger.h" + +AppOption Config::option; + +bool Config::init(std::string filename) +{ + NJson jsonroot; + bool ret = NJsonLoad(filename, jsonroot); + if (!ret) + { + XLOGE() << "[APP] load config failed, filename=" << filename; + return false; + } + XLOGI() << "[APP] load config success, filename=" << filename; + + if (jsonroot.contains("database")) + { + NJson json = jsonroot.at("database"); + option.database.host = json.contains("host") ? json.at("host") : ""; + option.database.port = json.contains("port") ? json.at("port") : 0; + option.database.user = json.contains("user") ? json.at("user") : ""; + option.database.passwd = json.contains("passwd") ? json.at("passwd") : ""; + option.database.dbname = json.contains("dbname") ? json.at("dbname") : ""; + + XLOGI() << "[APP] load database config end. host=" << option.database.host; + } + else + { + XLOGI() << "[APP] load database config error: not found. host=" << option.database.host; + } + + return true; +} \ No newline at end of file diff --git a/src/app/Config.h b/src/app/Config.h new file mode 100644 index 0000000..39d5a99 --- /dev/null +++ b/src/app/Config.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +struct DatabaseOption +{ + std::string host; + int port; + std::string user; + std::string passwd; + std::string dbname; +}; + +struct AppOption +{ + DatabaseOption database; +}; + +class Config +{ +public: + static bool init(std::string filename); + + + static AppOption option; +}; \ No newline at end of file diff --git a/src/app/Dao.cpp b/src/app/Dao.cpp index 284ce36..3701250 100644 --- a/src/app/Dao.cpp +++ b/src/app/Dao.cpp @@ -3,6 +3,8 @@ #include "common/Utils.h" #include "common/Snowflake.h" + + enum class EnDatabaseErr { SUCCESS = 0, @@ -13,7 +15,7 @@ std::shared_ptr DAO::get(std::string tableName) return std::make_shared(tableName); } -bool DAO::login(std::shared_ptr dao, std::string account, std::string passwd, std::string& err) +Errcode DAO::login(std::shared_ptr dao, std::string account, std::string passwd, std::string& err) { std::string t = Utils::timeNowStr(); if (!dao) @@ -24,7 +26,7 @@ bool DAO::login(std::shared_ptr dao, std::string account, std::string { err = "数据库连接错误"; DAO::writeSystemLog(dao, 2, "", account, "用户登录失败:" + err); - return false; + return Errcode::ERR_DB_CONN; } std::string sql = "SELECT * FROM user WHERE account='" + account + "';"; @@ -35,24 +37,24 @@ bool DAO::login(std::shared_ptr dao, std::string account, std::string { err = "数据库操作错误"; DAO::writeSystemLog(dao, 2, "", account, "用户登录失败:" + err); - return false; + return Errcode::ERR_DB_CONN; } if (res.size() <=0) { err = "用户不存在"; DAO::writeSystemLog(dao, 2, "", account, "用户登录失败:" + err); - return false; + return Errcode::ERR_LOGIN_USER_NOTEXIST; } DataFields& fields = res[0]; - std::string userId = fields.get_str("user_id"); - int loginCount = fields.get_int("login_count"); + std::string userId = fields.getStr("user_id"); + int loginCount = fields.getInt("login_count"); // 判断密码 - if (passwd != fields.get_str("passwd")) + if (passwd != fields.getStr("passwd")) { err = "密码错误"; DAO::writeSystemLog(dao, 2, userId, account, "用户登录失败:" + err); - return false; + return Errcode::ERR_LOGIN_PASSWD; } err = "登录成功"; @@ -66,7 +68,7 @@ bool DAO::login(std::shared_ptr dao, std::string account, std::string } DAO::writeSystemLog(dao, 2, userId, account, "用户登录成功"); - return true; + return Errcode::OK; } bool DAO::writeSystemLog(std::shared_ptr dao, int type, std::string userId, std::string account, std::string text) @@ -83,12 +85,12 @@ bool DAO::writeSystemLog(std::shared_ptr dao, int type, std::string u // 数据库写入登录日志 dao->setTableName("system_log"); DataFields fieldsLog; - fieldsLog.set("id", Snowflake::instance().nextIdStr()); + fieldsLog.set("log_id", Snowflake::instance().getIdStr()); fieldsLog.set("type", 2); fieldsLog.set("user_id", userId); fieldsLog.set("user_account", account); fieldsLog.set("content", text); - fieldsLog.set("log_time", Utils::timeNowStr()); + fieldsLog.set("create_time", Utils::timeNowStr()); bool ret = dao->insertFields({fieldsLog}); return ret; } @@ -114,7 +116,7 @@ int DAO::insertUser(DataFields& fields) return 1; } - std::string account = fields.get_str("account"); + std::string account = fields.getStr("account"); // step1: 查询 std::vector res; @@ -128,7 +130,7 @@ int DAO::insertUser(DataFields& fields) return 1; } - fields.set("user_id", Snowflake::instance().nextIdStr()); + fields.set("user_id", Snowflake::instance().getIdStr()); fields.set("create_time", Utils::timeNowStr()); ret = dao->insertFields(fields); return (ret) ? 0 : 1; diff --git a/src/app/Dao.h b/src/app/Dao.h index b697660..a95f15e 100644 --- a/src/app/Dao.h +++ b/src/app/Dao.h @@ -1,11 +1,13 @@ #include "database/DaoEntity.h" +#include "app/errcode.h" + class DAO { public: static std::shared_ptr get(std::string tableName=""); - static bool login(std::shared_ptr dao, std::string account, std::string passwd, std::string& err); + static Errcode login(std::shared_ptr dao, std::string account, std::string passwd, std::string& err); static bool writeSystemLog(std::shared_ptr dao, int type, std::string userId, std::string account, std::string text); diff --git a/src/app/Device.cpp b/src/app/Device.cpp index e69de29..5f7ea51 100644 --- a/src/app/Device.cpp +++ b/src/app/Device.cpp @@ -0,0 +1,126 @@ +#include "Device.h" + +#include "common/Logger.h" +#include "common/Utils.h" +#include "protocol/Communicator.h" +#include "common/JsonN.h" + +//int DeviceEntity::getAttrInt(std::string key) +//{ +// auto iter = mapAttrs.find(key); +// if (iter == mapAttrs.end()) { return 0; } +// return Utils::toInt(iter->second); +//} +// +//float DeviceEntity::getAttrFloat(std::string key) +//{ +// auto iter = mapAttrs.find(key); +// if (iter == mapAttrs.end()) { return 0.0f; } +// return Utils::toFloat(iter->second); +//} +// +//double DeviceEntity::getAttrDouble(std::string key) +//{ +// auto iter = mapAttrs.find(key); +// if (iter == mapAttrs.end()) { return 0.0; } +// return Utils::toDouble(iter->second); +//} +// +//std::string DeviceEntity::getAttrStr(std::string key) +//{ +// auto iter = mapAttrs.find(key); +// if (iter == mapAttrs.end()) { return ""; } +// return iter->second; +//} + +int DeviceEntity::startComm() +{ + if (!isOpen) + { + if (commEntity && commEntity->isAlive()) + { + commEntity->close(); + } + return 0; + } + + // 从属性列表中获取通讯方式和通讯地址、端口 + std::string commType = attrs.getStr("commType"); + + // 如果entity的通讯协议类型当前配置不一致,需要关闭连接删除通讯后创建新的通讯 + if (commEntity && commEntity->type != commType) + { + commEntity->close(); + commEntity = nullptr; + } + // 创建新的通讯 + if (!commEntity) + { + commEntity = Communicator::createEntity(attrs); + if (!commEntity) { return -1; } + } + + commEntity->start(); + return 0; +} + +// ================================================================================================ +// $$Device +std::map> Device::mapDevices; + +void Device::add(DataFields& fields) +{ + auto entity = std::make_shared(); + entity->deviceId = fields.getInt("device_id"); + entity->type = fields.getInt("type"); + entity->name = fields.getStr("name"); + entity->code = fields.getStr("code"); + entity->isOpen = fields.getInt("is_open"); + entity->attrsJson = fields.getStr("attrs"); + + // 解析属性的JSON字符串,转换成键值对 + NJson jsonroot; + bool ret = NJsonParse(entity->attrsJson, jsonroot); + if (!ret) // 解析错误 + { + XLOGE() << "device attr json parse error, device_id=" << entity->deviceId; + } + else + { + for (auto& [key, val] : jsonroot.items()) { + std::string valType = val.type_name(); + if (valType == "string") { + entity->attrs.set(key, val.get()); + } + else if (valType == "number") { + entity->attrs.set(key, val.get()); + } + else { + XLOGE() << key << ": [" << valType << "]"; + } + } + } + + // 保存设备 entity 到 map + if (entity->deviceId != -1) + { + mapDevices[entity->deviceId] = entity; + } + + // 启动通讯,该函数中会自动判断isOpen状态,选择是否进行通讯连接 + entity->startComm(); +} + +std::vector> Device::getDeviceByType(int type) +{ + std::vector> vecDevice; + for (auto iter = mapDevices.begin(); iter!=mapDevices.end(); ++iter) + { + auto device = iter->second; + if (device && (type<=0 || device->type == type)) + { + vecDevice.push_back(device); + } + } + return vecDevice; +} \ No newline at end of file diff --git a/src/app/Device.h b/src/app/Device.h index 2b1a1cf..325ae3a 100644 --- a/src/app/Device.h +++ b/src/app/Device.h @@ -1,7 +1,54 @@ #pragma once +#include +#include +#include +#include + +#include + +class CommEntity; + +class DeviceEntity +{ +public: + int deviceId = -1; + int type = -1; + std::string name; + std::string code; + bool isOpen = false; + std::string attrsJson = ""; + + + int err = 0; + int online = 0; + int status = 0; + + + //std::map mapAttrs; + DataFields attrs; + + // 通讯entity + std::shared_ptr commEntity; + + //int getAttrInt(std::string key); + //float getAttrFloat(std::string key); + //double getAttrDouble(std::string key); + //std::string getAttrStr(std::string key); + + // 启动通讯 + int startComm(); +}; + + class Device { public: - static bool init(); -}; \ No newline at end of file + static void add(DataFields& fields); + + static std::vector> getDeviceByType(int type); + +public: + static std::map> mapDevices; +}; + diff --git a/src/app/Operator.cpp b/src/app/Operator.cpp index d41f92c..880a2d0 100644 --- a/src/app/Operator.cpp +++ b/src/app/Operator.cpp @@ -4,9 +4,8 @@ bool Operator::login(std::string account, std::string passwd, std::string& err) { - bool ret = DAO::login(nullptr, account, passwd, err); - - if (ret) + auto ecode = DAO::login(nullptr, account, passwd, err); + if (ecode == Errcode::OK) { XLOGD() << "用户[" + account + "]登录成功"; } @@ -14,5 +13,5 @@ bool Operator::login(std::string account, std::string passwd, std::string& err) { XLOGD() << "用户[" + account + "]登录失败: " << err; } - return ret; + return (ecode == Errcode::OK); } \ No newline at end of file diff --git a/src/app/errcode.h b/src/app/errcode.h new file mode 100644 index 0000000..689445d --- /dev/null +++ b/src/app/errcode.h @@ -0,0 +1,15 @@ +#pragma once + +enum class Errcode +{ + OK = 0, + + ERR = 100, + + ERR_DB_CONN = 101, // 数据库连接错误 + ERR_DB_SQL = 102, // 数据库查询SQL错误 + + ERR_LOGIN_USER_NOTEXIST = 103, // 登入错误,用户不存在 + ERR_LOGIN_PASSWD = 104, // 登入错误,密码不正确 + +}; \ No newline at end of file diff --git a/src/cmake_msvc2019.bat b/src/cmake_msvc2019.bat index ad0b509..1d999bc 100644 --- a/src/cmake_msvc2019.bat +++ b/src/cmake_msvc2019.bat @@ -7,4 +7,4 @@ cd #buildmsvc2019 @REM Visual Studio 16 2019/Visual Studio 17 2022 @REM Win32/x64 -cmake ../src -G "Visual Studio 16 2019" -A x64 \ No newline at end of file +cmake ../src -G "Visual Studio 16 2019" -A Win32 \ No newline at end of file diff --git a/src/common/DataFields.cpp b/src/common/DataFields.cpp index 9f956fe..9524f59 100644 --- a/src/common/DataFields.cpp +++ b/src/common/DataFields.cpp @@ -3,45 +3,45 @@ void DataFields::set(string key, string val) { - map_fields_[key] = val; + mapFields_[key] = val; } void DataFields::set(string key, float val) { - map_fields_[key] = std::to_string(val); + mapFields_[key] = std::to_string(val); } void DataFields::set(string key, int val) { - map_fields_[key] = std::to_string(val); + mapFields_[key] = std::to_string(val); } void DataFields::set(string key, int64_t val) { - map_fields_[key] = std::to_string(val); + mapFields_[key] = std::to_string(val); } -string DataFields::get_str(string key) +string DataFields::getStr(string key) { - if (map_fields_.count(key) > 0) + if (mapFields_.count(key) > 0) { - return map_fields_[key]; + return mapFields_[key]; } else { return ""; } } -int DataFields::get_int(string key) +int DataFields::getInt(string key) { - return map_fields_.count(key) > 0 ? Utils::toInt(map_fields_[key]) : 0; + return mapFields_.count(key) > 0 ? Utils::toInt(mapFields_[key]) : 0; } -float DataFields::get_float(string key) +float DataFields::getFloat(string key) { - return map_fields_.count(key) > 0 ? Utils::toFloat(map_fields_[key]) : 0.0f; + return mapFields_.count(key) > 0 ? Utils::toFloat(mapFields_[key]) : 0.0f; } void DataFields::remove(string key) { - auto it = map_fields_.find(key); - if (it != map_fields_.end()) + auto it = mapFields_.find(key); + if (it != mapFields_.end()) { - map_fields_.erase(it); + mapFields_.erase(it); } } void DataFields::append(DataFields& datafield) @@ -49,19 +49,19 @@ void DataFields::append(DataFields& datafield) auto& map_f = datafield.fields(); for (auto it = map_f.begin(); it != map_f.end(); it++) { - map_fields_[it->first] = it->second; + mapFields_[it->first] = it->second; } } map& DataFields::fields() { - return map_fields_; + return mapFields_; } void DataFields::check(string key, string val, string d) { - if (map_fields_.count(key) > 0 && map_fields_[key] == val) + if (mapFields_.count(key) > 0 && mapFields_[key] == val) { - map_fields_[key] = d; + mapFields_[key] = d; } } @@ -69,7 +69,7 @@ string DataFields::get_insert_sql(string tbname) { string key; string val; - for (auto it = map_fields_.begin(); it != map_fields_.end(); it++) + for (auto it = mapFields_.begin(); it != mapFields_.end(); it++) { if (!key.empty()) { @@ -93,9 +93,9 @@ string DataFields::get_update_sql(string tbname, string sql_c) { ostringstream oss; oss << "update " << tbname << " set "; - for (auto iter = map_fields_.begin(); iter != map_fields_.end(); iter++) + for (auto iter = mapFields_.begin(); iter != mapFields_.end(); iter++) { - if (iter != map_fields_.begin()) + if (iter != mapFields_.begin()) { oss << ","; }; @@ -120,12 +120,12 @@ string DataFields::get_update_sql(string tbname, std::vector vec_ke ostringstream oss; oss << "update " << tbname << " set "; - for (auto iter = map_fields_.begin(); iter != map_fields_.end(); iter++) + for (auto iter = mapFields_.begin(); iter != mapFields_.end(); iter++) { auto& k = iter->first; auto& v = iter->second; if (!map_keys[k]) { continue; } - if (iter != map_fields_.begin()) + if (iter != mapFields_.begin()) { oss << ","; }; @@ -145,7 +145,7 @@ string DataFields::get_update_sql(string tbname, std::vector vec_ke void DataFields::foreach_item(function on_foraach) { - for (auto it = map_fields_.begin(); it != map_fields_.end(); it++) + for (auto it = mapFields_.begin(); it != mapFields_.end(); it++) { if (on_foraach) { @@ -153,15 +153,15 @@ void DataFields::foreach_item(function on_foraach) } } } -bool DataFields::is_empty(string key) +bool DataFields::isEmpty(string key) { - auto& s = map_fields_[key]; + auto& s = mapFields_[key]; return s.empty(); } bool DataFields::is_float_number(string key) { - auto& s = map_fields_[key]; + auto& s = mapFields_[key]; if (s.empty()) { return false; @@ -179,7 +179,7 @@ bool DataFields::is_float_number(string key) string DataFields::to_str() { string s; - for (auto it = map_fields_.begin(); it != map_fields_.end(); it++) + for (auto it = mapFields_.begin(); it != mapFields_.end(); it++) { s += ("[" + it->first + ":" + it->second + "] "); } @@ -188,10 +188,10 @@ string DataFields::to_str() int DataFields::size() { - return map_fields_.size(); + return mapFields_.size(); } void DataFields::clear() { - map_fields_.clear(); + mapFields_.clear(); } \ No newline at end of file diff --git a/src/common/DataFields.h b/src/common/DataFields.h index 1765d2c..d48826a 100644 --- a/src/common/DataFields.h +++ b/src/common/DataFields.h @@ -51,19 +51,19 @@ public: * 获取 string 值 * @param: [string key] 索引名称 */ - string get_str(string key); + string getStr(string key); /** * 获取 int 值 * @param: [string key] 索引名称 */ - int get_int(string key); + int getInt(string key); /** * 获取 float 值 * @param: [string key] 索引名称 */ - float get_float(string key); + float getFloat(string key); /** * 删除指定索引的值 @@ -122,7 +122,7 @@ public: * 判断是否含有数据项 * @param: [string key] 索引名称 */ - bool is_empty(string key); + bool isEmpty(string key); /** * 判断数据项是否是float数值类型 @@ -143,7 +143,7 @@ public: void clear(); private: - map map_fields_; + map mapFields_; }; #endif \ No newline at end of file diff --git a/src/common/JsonN.h b/src/common/JsonN.h new file mode 100644 index 0000000..821447e --- /dev/null +++ b/src/common/JsonN.h @@ -0,0 +1,67 @@ +#include +#include +#include + +using NJson = nlohmann::json; + +/// ============================================================================================= +/// 使用说明: + +/// 创建 ------------- +// json j; +// j["name"] = "Alice"; +// j["age"] = 25; +// j["address"] = {{"city", "Beijing"}, {"country", "China"}}; + +/// 字符串解析 ------------- +// json::parse(R"({"name": "Alice", "age": 25})"); +// 说明:解析失败会抛出异常 +// try { +// auto j = json::parse("invalid json"); +// } +// catch (json::parse_error& e) { +// std::cerr << "JSON 解析错误: " << e.what() << std::endl; +// } + +/// 文件解析 ------------- +// std::ifstream file("data.json"); +// json j; +// file >> j; + +/// 访问 JSON 数据 ------------- +// std::string name = j["name"]; // 直接访问 +// int age = j.at("age"); // 使用 at() 检查 key 是否存在 +// auto address = j["address"]; // 获取数组 + +/// 序列化为字符串 ------------- +// std::string json_str = j.dump(4); // 4 表示缩进,美化输出 + +static bool NJsonLoad(std::string jsonfile, NJson& json) +{ + std::ifstream ifs(jsonfile); + if (ifs.is_open()) + { + ifs >> json; + return true; + } + return false; +} + +static bool NJsonParse(std::string jsonstr, NJson& json) +{ + try + { + json = NJson::parse(jsonstr); + } + catch (nlohmann::json::parse_error& e) + { + //std::cerr << "JSON 解析错误: " << e.what() << std::endl; + return false; + } + return true; +} + +static bool NJsonLHas(NJson& json, std::string key) +{ + return json.contains("database"); +} diff --git a/src/common/Snowflake.h b/src/common/Snowflake.h index b7b90e9..46b3a8a 100644 --- a/src/common/Snowflake.h +++ b/src/common/Snowflake.h @@ -6,222 +6,205 @@ #include #include #include -#include // 如果不使用 mutex, 则开启下面这个定义, 但是我发现, 还是开启 mutex 功能, 速度比较快 // #define SNOWFLAKE_ID_WORKER_NO_LOCK - -/** - * @brief 分布式id生成类 - * https://segmentfault.com/a/1190000011282426 - * https://github.com/twitter/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala - * - * 64bit id: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 - * || || || | | | - * |└---------------------------时间戳--------------------------┘└中心-┘└机器-┘ └----序列号----┘ - * | - * 不用 - * SnowFlake的优点: 整体上按照时间自增排序, 并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分), 并且效率较高, 经测试, SnowFlake每秒能够产生26万ID左右. - */ + /** + * @brief 分布式id生成类 + * https://segmentfault.com/a/1190000011282426 + * https://github.com/twitter/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala + * + * 64bit id: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 + * || || || | | | + * |└---------------------------时间戳---------------------------┘└-中心-┘└机器-┘ └----序列号----┘ + * 不用 + * SnowFlake的优点: 整体上按照时间自增排序, 并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分), 并且效率较高, 经测试, SnowFlake每秒能够产生26万ID左右. + */ class Snowflake { public: - static Snowflake& instance() - { - static Snowflake sf; - return sf; - } - - typedef unsigned int UInt; - typedef unsigned long long int UInt64; + //typedef unsigned int UInt; + //typedef unsigned long long int UInt64; #ifdef SNOWFLAKE_ID_WORKER_NO_LOCK - typedef std::atomic AtomicUInt; - typedef std::atomic AtomicUInt64; + typedef std::atomic AtomicUInt; + typedef std::atomic AtomicUInt64; #else - typedef UInt AtomicUInt; - typedef UInt64 AtomicUInt64; + typedef unsigned int AtomicUInt; + typedef uint64_t AtomicUInt64; #endif - void set_worker_id(UInt workerId) - { - this->workerId = workerId; - } + static Snowflake& instance() + { + static Snowflake inst; + return inst; + } - void set_datacenter_id(UInt datacenterId) - { - this->datacenterId = datacenterId; - } + void setWorkerId(unsigned int workerId) { + this->workerId_ = workerId; + } - string nextIdStr() - { - return std::to_string(next_id()); - } + void setDatacenterId(unsigned int datacenterId) { + this->datacenterId_ = datacenterId; + } - /** - * 获得下一个ID (该方法是线程安全的) - * - * @return SnowflakeId - */ - UInt64 next_id() - { + uint64_t getId() { + return nextId(); + } + + std::string getIdStr() { + return std::to_string(nextId()); + } + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + uint64_t nextId() { #ifndef SNOWFLAKE_ID_WORKER_NO_LOCK - std::unique_lock lock {mutex}; - AtomicUInt64 timestamp {0}; + std::unique_lock lock {mutex}; + AtomicUInt64 timestamp {0}; #else - static AtomicUInt64 timestamp {0}; + static AtomicUInt64 timestamp {0}; #endif - timestamp = time_now_ms(); + timestamp = timetick(); - // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 - if (timestamp < lastTimestamp) - { - std::ostringstream s; - s << "clock moved backwards. Refusing to generate id for " << lastTimestamp - timestamp << " milliseconds"; - throw std::exception(std::runtime_error(s.str())); - } + // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp_) { + std::ostringstream s; + s << "clock moved backwards. Refusing to generate id for " << lastTimestamp_ - timestamp << " milliseconds"; + throw std::exception(std::runtime_error(s.str())); + } - if (lastTimestamp == timestamp) - { - // 如果是同一时间生成的,则进行毫秒内序列 - sequence = (sequence + 1) & sequenceMask; - if (0 == sequence) - { - // 毫秒内序列溢出, 阻塞到下一个毫秒,获得新的时间戳 - timestamp = next_millis(lastTimestamp); - } - } - else - { - sequence = 0; - } + if (lastTimestamp_ == timestamp) { + // 如果是同一时间生成的,则进行毫秒内序列 + sequence_ = (sequence_ + 1) & sequenceMask; + if (0 == sequence_) { + // 毫秒内序列溢出, 阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp_); + } + } + else { + sequence_ = 0; + } #ifndef SNOWFLAKE_ID_WORKER_NO_LOCK - lastTimestamp = timestamp; + lastTimestamp_ = timestamp; #else - lastTimestamp = timestamp.load(); + lastTimestamp_ = timestamp.load(); #endif - - // 移位并通过或运算拼到一起组成64位的ID - //return ((timestamp - twepoch) << timestampLeftShift) - // | (datacenterId << datacenterIdShift) - // | (workerId << workerIdShift) - // | sequence; - return ((timestamp - twepoch) << sequenceBits) - //| (datacenterId << datacenterIdShift) - //| (workerId << workerIdShift) - | sequence; - } + // 移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch_) << timestampLeftShift) + | (datacenterId_ << datacenterIdShift) + | (workerId_ << workerIdShift) + | sequence_; + } protected: - Snowflake() : workerId(0), datacenterId(0), sequence(0), lastTimestamp(0) - { - } + Snowflake() : workerId_(0), datacenterId_(0), sequence_(0), lastTimestamp_(0) {} - /** - * 返回以毫秒为单位的当前时间 - * - * @return 当前时间(毫秒) - */ - UInt64 time_now_ms() const - { - //auto t = std::chrono::time_point_cast(std::chrono::high_resolution_clock::now()); - auto t = std::chrono::time_point_cast(std::chrono::system_clock::now()); - return t.time_since_epoch().count(); - } + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + uint64_t timetick() const { + //auto t = std::chrono::time_point_cast(std::chrono::high_resolution_clock::now()); + auto t = std::chrono::time_point_cast(std::chrono::system_clock::now()); + return t.time_since_epoch().count(); + } - /** - * 阻塞到下一个毫秒,直到获得新的时间戳 - * - * @param lastTimestamp 上次生成ID的时间截 - * @return 当前时间戳 - */ - UInt64 next_millis(UInt64 lastTimestamp) const - { - UInt64 timestamp = time_now_ms(); - while (timestamp <= lastTimestamp) - { - timestamp = time_now_ms(); - } - return timestamp; - } + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + uint64_t tilNextMillis(uint64_t lastTimestamp) const { + uint64_t timestamp = timetick(); + while (timestamp <= lastTimestamp) { + timestamp = timetick(); + } + return timestamp; + } private: #ifndef SNOWFLAKE_ID_WORKER_NO_LOCK - std::mutex mutex; + std::mutex mutex; #endif - /** - * 开始时间截 (2010-01-01 00:00:00.000) 1262275200000 - */ - const UInt64 twepoch = 1262275200000; + /** + * 开始时间截 2025-01-01 00:00:00.000 + */ + const uint64_t twepoch_ = 1735660800000; - /** - * 机器id所占的位数 - */ - const UInt workerIdBits = 5; + /** + * 机器id所占的位数 + */ + const unsigned int workerIdBits = 4; - /** - * 数据中心id所占的位数 - */ - const UInt datacenterIdBits = 5; + /** + * 数据中心id所占的位数 + */ + const unsigned int datacenterIdBits = 1; - /** - * 序列所占的位数 - */ - const UInt sequenceBits = 12; + /** + * 序列所占的位数 + */ + const unsigned int sequenceBits = 12; - /** - * 机器ID向左移12位 - */ - const UInt workerIdShift = sequenceBits; + /** + * 机器ID向左移12位 + */ + const unsigned int workerIdShift = sequenceBits; - /** - * 数据标识id向左移17位 - */ - const UInt datacenterIdShift = workerIdShift + workerIdBits; + /** + * 数据标识id向左移16位 + */ + const unsigned int datacenterIdShift = workerIdShift + workerIdBits; - /** - * 时间截向左移22位 - */ - const UInt timestampLeftShift = datacenterIdShift + datacenterIdBits; + /** + * 时间截向左移17位 + */ + const unsigned int timestampLeftShift = datacenterIdShift + datacenterIdBits; - /** - * 支持的最大机器id,结果是31 - */ - const UInt maxWorkerId = -1 ^ (-1 << workerIdBits); + /** + * 支持的最大机器id, + */ + const unsigned int maxWorkerId = -1 ^ (-1 << workerIdBits); - /** - * 支持的最大数据中心id,结果是31 - */ - const UInt maxDatacenterId = -1 ^ (-1 << datacenterIdBits); + /** + * 支持的最大数据中心id, + */ + const unsigned int maxDatacenterId = -1 ^ (-1 << datacenterIdBits); - /** - * 生成序列的掩码,这里为4095 - */ - const UInt sequenceMask = -1 ^ (-1 << sequenceBits); + /** + * 生成序列的掩码,这里为4095 + */ + const unsigned int sequenceMask = -1 ^ (-1 << sequenceBits); - /** - * 工作机器id(0~31) - */ - UInt workerId; + /** + * 工作机器id(0~31) + */ + unsigned int workerId_; - /** - * 数据中心id(0~31) - */ - UInt datacenterId; + /** + * 数据中心id(0~31) + */ + unsigned int datacenterId_; - /** - * 毫秒内序列(0~4095) - */ - AtomicUInt sequence {0}; - - /** - * 上次生成ID的时间截 - */ - AtomicUInt64 lastTimestamp {0}; + /** + * 毫秒内序列(0~4095) + */ + AtomicUInt sequence_ {0}; + /** + * 上次生成ID的时间截 + */ + AtomicUInt64 lastTimestamp_ {0}; }; -#endif // _JW_CORE_ID_WORKER_H_ + +#endif // _JW_CORE_ID_WORKER_H_ \ No newline at end of file diff --git a/src/database/DaoEntity.cpp b/src/database/DaoEntity.cpp index 54b7a87..91d1155 100644 --- a/src/database/DaoEntity.cpp +++ b/src/database/DaoEntity.cpp @@ -2,7 +2,7 @@ //#include "PvInstance.h" //#include "spdlogger.h" -MysqlOptions DaoEntity::options_; +MysqlOption DaoEntity::option_; DaoEntity::DaoEntity(string tb_name) { @@ -12,7 +12,7 @@ DaoEntity::DaoEntity(string tb_name) //opts.password = "123456"; //opts.port = 3306; //opts.dbname = "pvb"; - db_ = make_shared(DaoEntity::options_); + db_ = make_shared(DaoEntity::option_); if (!db_->isConnected()) { //Global::data().status_msg = "数据库连接异常!"; @@ -26,9 +26,17 @@ DaoEntity::~DaoEntity() db_ = nullptr; } -MysqlOptions& DaoEntity::mysqlOptions() +MysqlOption& DaoEntity::mysqlOption() { - return DaoEntity::options_; + return DaoEntity::option_; +} +void DaoEntity::setOption(std::string host, int port, std::string user, std::string pwd, std::string dbname) +{ + option_.host = host; + option_.port = port; + option_.user = user; + option_.password = pwd; + option_.dbname = dbname; } std::shared_ptr DaoEntity::create(string tb_name) @@ -39,13 +47,13 @@ std::shared_ptr DaoEntity::create(string tb_name) bool DaoEntity::execOnce(string sql) { - auto db = make_shared(DaoEntity::options_); + auto db = make_shared(DaoEntity::option_); return db->exec(sql); } bool DaoEntity::execOnce(string sql, vector& result) { - auto db = make_shared(DaoEntity::options_); + auto db = make_shared(DaoEntity::option_); return db->exec(sql, result); } @@ -141,7 +149,7 @@ bool DaoEntity::duplicateUpdate(DataFields& fields, vector& keys) { s_data += ","; } - s_data += (k + "='" + fields.get_str(k) + "'"); + s_data += (k + "='" + fields.getStr(k) + "'"); } string sql = "INSERT INTO " + tableName_ + "(" + s_key + ") VALUES (" + s_val + ") ON duplicate KEY UPDATE " + s_data; return this->db_->exec(sql); @@ -178,7 +186,7 @@ bool DaoEntity::queryFields(string keys, const string& sql_c, PageInfo& pageinfo pageinfo.total = 0; return true; } - pageinfo.total = res_total[0].get_int("total"); + pageinfo.total = res_total[0].getInt("total"); if (pageinfo.total <= 0) { return true; diff --git a/src/database/DaoEntity.h b/src/database/DaoEntity.h index 2fe24a1..cdaa374 100644 --- a/src/database/DaoEntity.h +++ b/src/database/DaoEntity.h @@ -9,7 +9,8 @@ public: DaoEntity(string tableName); ~DaoEntity(); - static MysqlOptions& mysqlOptions(); + static MysqlOption& mysqlOption(); + static void setOption(std::string host, int port, std::string user, std::string pwd, std::string dbname); static std::shared_ptr create(string tableName); @@ -99,7 +100,7 @@ public: bool updateFields(DataFields& fields, vector vecKeys, const string& cond); protected: - static MysqlOptions options_; + static MysqlOption option_; // mysql 数据库操作对象 std::shared_ptr db_ = nullptr; diff --git a/src/database/MysqlClient.cpp b/src/database/MysqlClient.cpp index f284af0..7f87e1c 100644 --- a/src/database/MysqlClient.cpp +++ b/src/database/MysqlClient.cpp @@ -3,7 +3,7 @@ //#include "Spdlogger.h" #include "Logger.h" -MysqlClient::MysqlClient(MysqlOptions opts) : options_(opts) +MysqlClient::MysqlClient(MysqlOption option) : option_(option) { conn(); } @@ -20,9 +20,10 @@ int MysqlClient::conn() return 0; } mysql_ = mysql_init(nullptr); - MYSQL* ret = mysql_real_connect(mysql_, options_.host.c_str(), options_.user.c_str(), options_.password.c_str(), options_.dbname.c_str(), options_.port, NULL, 0); + MYSQL* ret = mysql_real_connect(mysql_, option_.host.c_str(), option_.user.c_str(), option_.password.c_str(), option_.dbname.c_str(), option_.port, NULL, 0); if (ret == NULL) { + std::string err = mysql_error(mysql_); //Spdlogger::info("[mysql] connect failed: {}", mysql_error(mysql_)); mysql_ = nullptr; } @@ -49,7 +50,7 @@ void MysqlClient::close() bool MysqlClient::exec(std::string sql) { - XLOGD() << "Mysql exec sql=" << sql; + //XLOGD() << "Mysql exec sql=" << sql; if (!mysql_) { XLOGE() << "Mysql exec error, database is not connected."; diff --git a/src/database/MysqlClient.h b/src/database/MysqlClient.h index 0acd434..c76b882 100644 --- a/src/database/MysqlClient.h +++ b/src/database/MysqlClient.h @@ -15,7 +15,7 @@ using namespace std; -struct MysqlOptions +struct MysqlOption { std::string host; std::string user; @@ -27,7 +27,7 @@ struct MysqlOptions class MysqlClient { public: - MysqlClient(MysqlOptions options); + MysqlClient(MysqlOption option); ~MysqlClient(); /** @@ -62,7 +62,7 @@ private: MYSQL* mysql_ = nullptr; // 数据库连接信息 - MysqlOptions options_; + MysqlOption option_; }; #endif // !!! _DbMysql_H_ diff --git a/src/main.cpp b/src/main.cpp index 6a75895..a788372 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -9,46 +10,62 @@ #include "app/Application.h" #include "app/Dao.h" +#include "common/Snowflake.h" +#include "protocol/TcpEntity.h" + +#include "common/JsonN.h" + +#include "app/Config.h" + int main(int argc, char** argv) { - SetConsoleOutputCP(CP_UTF8); // 设置控制台输出为UTF-8 + // 设置控制台输出为 UTF-8 编码 + SetConsoleOutputCP(CP_UTF8); + // 设置控制台输入为 UTF-8 编码(如果需要输入中文) + SetConsoleCP(CP_UTF8); - auto& mysqlOptions = DaoEntity::mysqlOptions(); - mysqlOptions.host = "localhost"; - mysqlOptions.port = 3306; - mysqlOptions.user = "root"; - mysqlOptions.password = "123456"; - mysqlOptions.dbname = "ees"; + NJson jsonroot; + NJsonParse(R"({"name": "Alice", "age": 25, 111,})", jsonroot); + std::cout << (jsonroot.is_null() ? "ERROR" : "OK") << std::endl; - std::string filename = "assets/html/data中文.txt"; - - std::filesystem::path filePath = std::filesystem::u8path("assets/html/data中文.txt"); - //std::locale::global(locale(""));//将全局区域设为操作系统默认区域 - std::ifstream ifs(filePath, std::ios::binary); - //std::locale::global(locale("C"));//还原全局区域设定 - - if (ifs.is_open()) - { - // 将文件指针移动到末尾获取文件大小 - ifs.seekg(0, std::ios::end); - std::streamsize size = ifs.tellg(); - ifs.seekg(0, std::ios::beg); - - std::string buf(size, '\0'); - ifs.read(&buf[0], size); - std::cout << "文件内容: " << buf << std::endl; - } - else - { - std::cout << "error" << std::endl; - } - - + ////std::cout << Snowflake::instance().getId() << std::endl; + //for (int i = 0; i<=10; ++i) { + // //std::cout << Snowflake::instance().getId() << std::endl; + //} + //DaoEntity dao(""); + // + //std::string filename = "assets/html/data中文.txt"; + // + //std::filesystem::path filePath = std::filesystem::u8path("assets/html/data中文.txt"); + ////std::locale::global(locale(""));//将全局区域设为操作系统默认区域 + //std::ifstream ifs(filePath, std::ios::binary); + ////std::locale::global(locale("C"));//还原全局区域设定 - // 运行后台服务 + //if (ifs.is_open()) + //{ + // // 将文件指针移动到末尾获取文件大小 + // ifs.seekg(0, std::ios::end); + // std::streamsize size = ifs.tellg(); + // ifs.seekg(0, std::ios::beg); + + // std::string buf(size, '\0'); + // ifs.read(&buf[0], size); + // std::cout << "文件内容: " << buf << std::endl; + //} + //else + //{ + // std::cout << "error" << std::endl; + //} + + //TcpEntity tcpEntity; + //tcpEntity.setHost("127.0.0.1", 9901, true); + //tcpEntity.setReconnect(5000); + //tcpEntity.start(); + + // 运行后台服务 Application::instance().init(); // 运行QT主窗口 @@ -58,5 +75,9 @@ int main(int argc, char** argv) mainWin.resize(1920, 1080); mainWin.show(); qapp.exec(); + + //while (1) { + // std::this_thread::sleep_for(std::chrono::seconds(1)); + //} return 0; } \ No newline at end of file diff --git a/src/protocol/Communicator.cpp b/src/protocol/Communicator.cpp new file mode 100644 index 0000000..510d885 --- /dev/null +++ b/src/protocol/Communicator.cpp @@ -0,0 +1,28 @@ +#include "Communicator.h" +#include "TcpEntity.h" + +std::shared_ptr Communicator::createEntity(DataFields& data) +{ + std::string commType = data.getStr("commType"); + std::string ip = data.getStr("ip"); + int port = data.getInt("port"); + int isclient = data.getInt("isclient"); + + if (commType == "TCP") + { + auto entity = std::make_shared(); + entity->setHost(ip, port, isclient); + return entity; + } + else if (commType == "MODBUS") + { + } + else if (commType == "ACTIVEX") + { + } + else if (commType == "SDK") + { + } + + return nullptr; +} \ No newline at end of file diff --git a/src/protocol/Communicator.h b/src/protocol/Communicator.h new file mode 100644 index 0000000..c9b0e2e --- /dev/null +++ b/src/protocol/Communicator.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "common/DataFields.h" + +class CommEntity +{ +public: + CommEntity() {} + CommEntity(std::string type) : type(type) {} + + void setType(std::string type) { this->type = type; } + + // 启动通讯连接 + virtual int start() { return 0; }; + // 关闭通讯连接 + virtual void close() { isCloseRequest_ = true; }; + + std::string id() { return id_; } + + bool isAlive() { return isAlive_; } + bool isConnected() { return isConnected_; } + +public: + std::string id_; + bool isAlive_ = false; + bool isConnected_ = false; + bool isCloseRequest_ = false; + std::string type; +}; + + +class Communicator +{ +public: + static std::shared_ptr createEntity(DataFields& data); +}; \ No newline at end of file diff --git a/src/protocol/TcpEntity.cpp b/src/protocol/TcpEntity.cpp new file mode 100644 index 0000000..f6bd2c1 --- /dev/null +++ b/src/protocol/TcpEntity.cpp @@ -0,0 +1,281 @@ +#include "TcpEntity.h" +#include "common/Utils.h" +#include +#include +#include + +static std::string ToHexText(std::string s) +{ + std::stringstream ss; + for (unsigned int i = 0; i < s.size(); ++i) + { + unsigned char ch = s[i]; + ss << std::setw(2) << std::setfill('0') << std::hex << std::uppercase << (int)ch << ((i < s.size() - 1) ? " " : ""); + } + return ss.str(); +} + +TcpEntity::TcpEntity(TcpHandler* handler) + : handler_(handler), isClient_(true) +{ +} + +TcpEntity::~TcpEntity() +{ +} + +void TcpEntity::setHandler(TcpHandler* handler) +{ + handler_ = handler; +} + +void TcpEntity::setHost(string host, int port, bool isClient) +{ + host_ = host; + port_ = port; + isClient_ = isClient; +} + + +void TcpEntity::setReconnect(int ms) +{ + tReconnect_ = ms; +} + +int TcpEntity::start() +{ + if (isAlive_) + { + return 1; + } + isAlive_ = true; + + WSADATA wsaData; + if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) + { + return -1; + } + + sockaddr_.sin_family = AF_INET; + sockaddr_.sin_port = htons(port_); + sockaddr_.sin_addr.S_un.S_addr = (isClient_ ? inet_addr(host_.c_str()) : htonl(INADDR_ANY)); + + std::thread([=]() { this->runThreadTcp(); }).detach(); + return 0; +} + +void TcpEntity::runThreadTcp() +{ + //if (isRequestClose_) { break; } + //std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + std::cout << "TCP thread start ..." << std::endl; + if (isClient_) + { + this->runClientLoop(); + } + else + { + this->runServerLoop(); + } + isAlive_ = false; +} + +void TcpEntity::close() +{ + isCloseRequest_ = true; +} + +void TcpEntity::runServerLoop() +{ + sock_ = ::socket(AF_INET, SOCK_STREAM, 0); + // 绑定套接字 【注意】functional中定义了bind与winsock2的定义发生重载导致异常,这里需要使用::bind(加::) + if (::bind(sock_, (SOCKADDR*)&sockaddr_, sizeof(SOCKADDR)) == SOCKET_ERROR) + { + std::cout << "TCP server bind [" << hostport() << "] failed." << std::endl; + return; + } + // 启动监听,准备接收客户请求 + if (::listen(sock_, 5) == SOCKET_ERROR) + { + std::cout << "TCP server listen [" << hostport() << "] failed." << std::endl; + return; + } + + int addrlen = sizeof(SOCKADDR); + while (1) + { + if (isCloseRequest_) { break; } + + Client client; + + // 等待客户请求到来 + client.sock = ::accept(sock_, (SOCKADDR*)&client.sock_addr, &addrlen); + if (client.sock == INVALID_SOCKET) + { + break; + } + + client.host = inet_ntoa(client.sock_addr.sin_addr); + + // 存储客户端的连接信息 + vecClient_.push_back(client); + + // 创建线程处理 + std::thread th([=]() { this->runServerRecvLoop(client, client.host); }); + th.detach(); + } + ::closesocket(sock_); + + // 连接关闭 + for (auto iter = vecClient_.begin(); iter != vecClient_.end(); ++iter) + { + ::closesocket(iter->sock); + vecClient_.erase(iter); + } + isCloseRequest_ = false; +} + +void TcpEntity::runServerRecvLoop(Client client, std::string client_name) +{ + std::vector buf(1024000, 0); + while (1) + { + if (isCloseRequest_ || !isAlive_) + { + break; + } + memset(buf.data(), 0, buf.size()); + // 接收数据 + int n = ::recv(client.sock, &buf[0], buf.size(), 0); + // 需要判断 errno是否等于 EINTR 。如果errno == EINTR 则说明recv函数是由于程序接收到信号后返回的,socket连接还是正常的 + if (n <= 0 && GetLastError() != EINTR) + { + // 客户端连接异常(关闭) + break; + } + else + { + } + } + // 连接关闭 + for (auto iter = vecClient_.begin(); iter != vecClient_.end(); ++iter) + { + if (iter->sock == client.sock) + { + vecClient_.erase(iter); + break; + } + } +} + +void TcpEntity::runClientLoop() +{ + // 数据缓存 + std::vector buf(1024000, 0); + + while (1) + { + if (isCloseRequest_) { break; } + + //创建套接字,向服务器发出连接请求 + sock_ = ::socket(AF_INET, SOCK_STREAM, 0); + if (::connect(sock_, (SOCKADDR*)&sockaddr_, sizeof(SOCKADDR)) != SOCKET_ERROR) + { + isConnected_ = true; + std::cout << "TCP client connect to [" << hostport() << "] success." << std::endl; + + // 连接服务器成功,循环等待接受消息 + while (1) + { + if (isCloseRequest_) { break; } + + memset(buf.data(), 0, buf.size()); + int n = ::recv(sock_, buf.data(), buf.size(), 0); + if (n <= 0 && GetLastError() != EINTR) + { + // TCP通讯异常, 关闭连接 + ::closesocket(sock_); + isConnected_ = false; + break; + } + else + { + // 处理消息 + } + } + } + else + { + isConnected_ = false; + std::cout << "TCP client connect to [" << hostport() << "] failed." << std::endl; + } + + // 连接异常 + if (tReconnect_ > 0) + { + // 重新连接 + std::cout << "TCP client [" << hostport() << "] reconnect (" << tReconnect_ << ")." << std::endl; + //std::this_thread::sleep_for(std::chrono::microseconds(tReconnect_)); + Sleep(tReconnect_); + } + else + { + // 关闭线程 + std::cout << "TCP client [" << hostport() << "] close." << std::endl; + break; + } + } + if (sock_ != INVALID_SOCKET) + { + ::closesocket(sock_); + sock_ = INVALID_SOCKET; + isConnected_ = false; + } + if (isCloseRequest_) + { + } + isCloseRequest_ = false; +} + +bool TcpEntity::sendData(std::string data, std::string clientId) +{ + if (isClient_) + { + // #客户端 + if (sock_ == INVALID_SOCKET) + { + //Spdlogger::error("TCP client send data failed, connect error, invalid socket, device: {}:{}.", this->type_, client_code); + return false; + } + int len = ::send(sock_, data.c_str(), data.size(), 0); + //Spdlogger::info("TCP client send data success, data length={}, device: {}:{}.", len, this->type_, client_code); + return (len > 0); + } + else + { + if (vecClient_.size() <= 0) + { + return false; + } + for (auto& client : vecClient_) + { + std::string client_addr = inet_ntoa(client.sock_addr.sin_addr); + ::send(client.sock, data.c_str(), data.size(), 0); + + } + + return true; + } +} + + +bool TcpEntity::isAlive() +{ + return isAlive_; +} + +bool TcpEntity::isConnected() +{ + return isConnected_; +} diff --git a/src/protocol/TcpEntity.h b/src/protocol/TcpEntity.h new file mode 100644 index 0000000..0fa2521 --- /dev/null +++ b/src/protocol/TcpEntity.h @@ -0,0 +1,117 @@ +#ifndef _TcpEntity_H_ +#define _TcpEntity_H_ + +#include +#include +#include +#include +#include + +#include "Communicator.h" + +using namespace std; + +enum class ETcpEvent +{ + NUL = 0, // + SRV_SUCCESS, // 服务端启动成功(listen成功) + ACCEPT, // 服务端接收到客户端的连接 + CONN_OK, // 作为客户端时,连接成功 + CONN_ERR, // 作为客户端时,连接失败 + MSG_RECV, // 接收消息 + MSG_SEND, // 发送消息 + CLOSE, // 断开连接 + ERR // 异常错误 +}; + +class TcpHandler; +class TcpParser; + +class TcpEntity : public CommEntity, public std::enable_shared_from_this +{ +public: + struct Client + { + SOCKET sock; + SOCKADDR_IN sock_addr; + std::string host; + std::shared_ptr parser = nullptr; + }; + +public: + // 初始化服务端 + TcpEntity(TcpHandler* handler = nullptr); + ~TcpEntity(); + + int start() override; + void close() override; + + void runThreadTcp(); + + void setHost(string host, int port, bool is_client); + std::string host() { return host_; } + int port() { return port_; } + std::string hostport() { return host_ + ":" + std::to_string(port_); } + + void setReconnect(int ms); + + bool isClient() { return isClient_; } + + void setHandler(TcpHandler* handler); + + bool sendData(std::string data, std::string clientId=""); + + bool isAlive(); + bool isConnected(); + + + std::shared_ptr parser = nullptr; + +private: + void runServerLoop(); + void runServerRecvLoop(Client client, std::string client_name); + + void runClientLoop(); + +private: + // 本机的SOCKET对象 + SOCKET sock_ = INVALID_SOCKET; + + // socket addr信息 + SOCKADDR_IN sockaddr_; + + // TCP类型是否是客户端: true: 客户端, false: 服务端 + bool isClient_ = true; + + // 通讯地址,作为客户端时有效 + std::string host_; + + // 通讯端口 + int port_ = 0; + + // 重连间隔时间,单位秒 + int tReconnect_ = 0; + + // 作为服务端时连接的客户端SOCKET + std::vector vecClient_; + + // 回调处理对象 + TcpHandler* handler_ = nullptr; + + bool isAlive_ = false; + bool isCloseRequest_ = false; + bool isConnected_ = false; + + // 状态更新时间戳 + int64_t ts_; + + int64_t tsHeartbeat_=0; +}; + +class TcpHandler +{ +public: + virtual void onEvent(TcpEntity* entity, TcpEntity::Client* client, ETcpEvent evt, std::string msg) {}; +}; + +#endif // !_TcpEntity_H_ \ No newline at end of file diff --git a/src/widgets/MainWindow.cpp b/src/widgets/MainWindow.cpp index 29e57a0..295c315 100644 --- a/src/widgets/MainWindow.cpp +++ b/src/widgets/MainWindow.cpp @@ -80,27 +80,12 @@ void Menu::onMenuBtnClicked() MainWindow::MainWindow() { - webView = std::make_shared(this); - - MyWebHandler* myWebHandler = new MyWebHandler(); - QWebChannel* webChannel = new QWebChannel(); - webChannel->registerObject("cppNative", myWebHandler); - webView->page()->setWebChannel(webChannel); - - webView->setGeometry(0, 0, 1920, 1080); - // 默认设置透明, 解决加载时的白屏闪烁 - webView->page()->setBackgroundColor(Qt::transparent); - - webView->setContextMenuPolicy(Qt::NoContextMenu); - //webView.load(QUrl("https://www.baidu.com")); - webView->load(QUrl("file:///assets/html/main.html")); - //connect(wWebView.get(), &QWebEngineView::loadFinished, this, &MyWidget::slotLoadFinished); - - //std::string htmlContent = "HelloWorld"; - //webView->setHtml(htmlContent.c_str(), QUrl("file:///assets/html/")); - webView->show(); - return; - + if (1) + { + this->initWebView(); + return; + } + QUI::label(labBkg_, this, 0, 0, 1920, 1080, ""); labBkg_.setPixmap(QPixmap("assets/ui/bkg01.png")); @@ -130,6 +115,29 @@ MainWindow::MainWindow() timer_.start(1000); } +void MainWindow::initWebView() +{ + webView_ = std::make_shared(this); + + MyWebHandler* myWebHandler = new MyWebHandler(); + QWebChannel* webChannel = new QWebChannel(); + webChannel->registerObject("cppNative", myWebHandler); + webView_->page()->setWebChannel(webChannel); + + webView_->setGeometry(0, 0, 1920, 1080); + // 默认设置透明, 解决加载时的白屏闪烁 + webView_->page()->setBackgroundColor(Qt::transparent); + + webView_->setContextMenuPolicy(Qt::NoContextMenu); + //webView.load(QUrl("https://www.baidu.com")); + webView_->load(QUrl("file:///assets/html/main.html")); + //connect(wWebView.get(), &QWebEngineView::loadFinished, this, &MyWidget::slotLoadFinished); + + //std::string htmlContent = "HelloWorld"; + //webView->setHtml(htmlContent.c_str(), QUrl("file:///assets/html/")); + webView_->show(); +} + void MainWindow::initMenu() { std::vector vecMenuId = @@ -146,9 +154,9 @@ void MainWindow::initMenu() void MainWindow::resizeEvent(QResizeEvent* event) { auto& size = event->size(); - if (webView) + if (webView_) { - webView->resize(size); + webView_->resize(size); } } diff --git a/src/widgets/MainWindow.h b/src/widgets/MainWindow.h index e6cd999..13563d8 100644 --- a/src/widgets/MainWindow.h +++ b/src/widgets/MainWindow.h @@ -30,6 +30,8 @@ public: MainWindow(); void initMenu(); + void initWebView(); + void resizeEvent(QResizeEvent* event); public slots: @@ -51,5 +53,5 @@ public: std::shared_ptr menu_ = nullptr; - std::shared_ptr webView; + std::shared_ptr webView_; }; \ No newline at end of file diff --git a/src/widgets/WebHandler.cpp b/src/widgets/WebHandler.cpp index 1c7e63b..c6197cf 100644 --- a/src/widgets/WebHandler.cpp +++ b/src/widgets/WebHandler.cpp @@ -7,6 +7,21 @@ #include "common/Logger.h" #include "Snowflake.h" #include "app/Dao.h" +#include "app/Admin.h" +#include "app/Device.h" + +static void VariantListRes(std::vector& data, QVariantList& listRes) +{ + for (auto& fields: data) + { + QVariantMap row; + for (auto& field: fields.fields()) + { + row[field.first.c_str()] = field.second.c_str(); + } + listRes << row; + } +} static void JSsetResPaginaion(QVariantMap& result, std::vector& data, int page, int pageSize, int count, int code, string err) { @@ -47,16 +62,16 @@ void MyWebHandler::log(const QString& text) QString MyWebHandler::readFile(const QString& filename) { - //std::string filePath = "assets/html/系统管理/index.html"; //filename.toStdString(); - std::filesystem::path filePath = std::filesystem::u8path(filename.toStdString()); - XLOGD() << "[cppNative] readFile: " << filePath; + std::string fileName = filename.toStdString(); + XLOGD() << "[cppNative] readFile: " << fileName; + std::filesystem::path filePath = std::filesystem::u8path(fileName); std::ifstream ifs(filePath); if (ifs.is_open()) { // 获取文件大小 ifs.seekg(0, std::ios::end); std::streamsize size = ifs.tellg(); - XLOGD() << "[cppNative] readFile [" << filePath << "] success, data size=" << size; + XLOGD() << "[cppNative] readFile [" << fileName << "] success, data size=" << size; // 定位回文件开始,读取文件内容到缓冲区 ifs.seekg(0, std::ios::beg); @@ -67,11 +82,23 @@ QString MyWebHandler::readFile(const QString& filename) } else { - XLOGD() << "[cppNative] readFile [" << filePath << "] failed."; + XLOGD() << "[cppNative] readFile [" << fileName << "] failed."; } return ""; } +void MyWebHandler::login(const QString& username, const QString& passwd) +{ + XLOGI() << "login request: " << username.toStdString(); + Errcode ecode = Admin::instance().longin(username.toStdString(), passwd.toStdString()); + + //std::this_thread::sleep_for(std::chrono::milliseconds(10000)); + emit signalLongin(username, int(ecode)); +} + +void MyWebHandler::loginOut(const QString& username) +{ +} QVariantMap MyWebHandler::queryUserList(int page, int pageSize) { @@ -102,7 +129,7 @@ int MyWebHandler::insertUser(QVariantMap params) // 设置用户角色 - std::string user_id = fields.get_str("user_id"); + std::string user_id = fields.getStr("user_id"); if (params.contains("role_id")) { int role_id = params["role_id"].toInt(); @@ -161,16 +188,24 @@ int MyWebHandler::updateUser(const QString& userId, QVariantMap params) // 角色管理接口 QVariantMap MyWebHandler::queryRoleList(int page, int pageSize) { + QVariantMap result; + std::vector res; auto dao = DAO::get("role"); bool ret = dao->exec("SELECT * FROM role;", res); - - QVariantMap result; JSsetResPaginaion(result, res, page, pageSize, res.size(), 0, "操作成功"); - XLOGD() << "[cppNative] queryRoleList " << (ret ? "success." : "failed."); + + //QtConcurrent::run([this]() + // { + // XLOGD() << "[cppNative] lxy ========== queryRoleList 1111111111111111111111 "; + // QThread::msleep(2000); + // XLOGD() << "[cppNative] lxy ========== queryRoleList 2222222222222222222222 "; + // }); + return result; } + int MyWebHandler::insertRole(QVariantMap params) { DataFields fields; @@ -313,6 +348,66 @@ QVariantMap MyWebHandler::queryDeviceList(int page, int pageSize) return result; } +static void JSgetReqParamSql(QString key, QVariantMap& params, std::string& sql) +{ + if (params.contains(key)) + { + auto& v = params[key]; + std::string typeName = v.typeName(); + + + XLOGD() << key.toStdString() << " : " << typeName; + if (!sql.empty()) sql += ","; + + if ("QVariantList" == typeName) + { + std::string str = ""; + for (auto& item : v.toList()) + { + if (!str.empty()) str += ","; + str += ("'" + item.toString().toStdString() + "'"); + } + sql += ("`" + key.toStdString() + "` IN (" + str + ")"); + XLOGD() << "QVariantList"; + } + else if ("QString" == typeName) + { + // 如果是数组, 需要处理数组格式: ["","",""] + sql += ("`" + key.toStdString() + "`='" + v.toString().toStdString() + "'"); + XLOGD() << "QString"; + } + else { + XLOGD() << "???"; + } + } +} + +QVariantList MyWebHandler::queryDevice(QVariantMap params) +{ + XLOGD() << "MyWebHandler::queryDevice -- params.size=" << params.size(); + + QVariantList result; + + std::string sqlc = ""; + JSgetReqParamSql("type", params, sqlc); + + if (sqlc.empty()) { + return result; + } + + std::string sql = "SELECT * FROM device WHERE " + sqlc + ";"; + auto dao = DAO::get("device"); + std::vector res; + bool ret = dao->exec(sql, res); + XLOGD() << "sql=" << sql; + XLOGD() << "queryDevice: size=" << res.size(); + + VariantListRes(res, result); + + XLOGD() << "queryDevice: result size=" << result.size(); + return result; +} + int MyWebHandler::insertDevice(QVariantMap params) { DataFields fields; @@ -600,5 +695,31 @@ QVariantMap MyWebHandler::querySecRecordList(int page, int pageSize) return result; }; -int MyWebHandler::insertSecRecord(QVariantMap params) {}; -int MyWebHandler::updateSecRecord(const QString& policyId, QVariantMap params) {}; \ No newline at end of file +int MyWebHandler::insertSecRecord(QVariantMap params) { return 0; }; +int MyWebHandler::updateSecRecord(const QString& policyId, QVariantMap params) { return 0; }; + + +QVariantList MyWebHandler::getDeviceInfo(const QVariantList& types) +{ + std::vector> vecDevice; + for (auto item: types) + { + int deviceType = item.toInt(); + auto vecRes = Device::getDeviceByType(deviceType); + vecDevice.insert(vecDevice.end(), vecRes.begin(), vecRes.end()); + } + + QVariantList result; + for (auto& device: vecDevice) + { + QVariantMap row; + row["device_id"] = device->deviceId; + row["name"] = device->name.c_str(); + row["type"] = device->type; + row["status"] = device->status; + row["online"] = device->online; + row["err"] = device->err; + result << row; + } + return result; +} \ No newline at end of file diff --git a/src/widgets/WebHandler.h b/src/widgets/WebHandler.h index 3e32989..097456c 100644 --- a/src/widgets/WebHandler.h +++ b/src/widgets/WebHandler.h @@ -4,6 +4,7 @@ #include #include #include +#include class MyWebHandler : public QObject { @@ -22,7 +23,11 @@ signals: //在C++中定义的信号,可以在JS端监听此信号接收消息 void signalNativeTextChanged(const QString& text); - void singalReadFileFinished(const QString& text); + void signalReadFileFinished(const QString& text); + + void signalLongin(const QString& username, const int& ecode); + + void signalLonout(); public slots: //C++ 端的公共槽函数,可以在JS端调用。 @@ -32,6 +37,13 @@ public slots: QString readFile(const QString& filename); + + // ================================================================================================================ + // 登录,返回用户信息 + void login(const QString& username, const QString& password); + // 登出 + void loginOut(const QString& username); + // ================================================================================================================ // 用户管理接口 QVariantMap queryUserList(int page, int pageSize); @@ -64,6 +76,8 @@ public slots: // ================================================================================================================ // 设备管理接口 QVariantMap queryDeviceList(int page, int pageSize); + + QVariantList queryDevice(QVariantMap params); int insertDevice(QVariantMap params); int deleteDevice(const QString& deviceId); int updateDevice(const QString& deviceId, QVariantMap params); @@ -101,6 +115,9 @@ public slots: int insertSecRecord(QVariantMap params); int updateSecRecord(const QString& policyId, QVariantMap params); + // ================================================================================================================ + QVariantList getDeviceInfo(const QVariantList& types); + public: QString nativeText_; };