From 203885c8c83257f7aa4f25f95f589d731e69f5e2 Mon Sep 17 00:00:00 2001 From: fasiondog Date: Wed, 14 Apr 2021 01:49:02 +0800 Subject: [PATCH] hkuserver continuue --- .../db_connect/sqlite/SQLiteConnect.cpp | 2 +- hikyuu_cpp/hikyuu_server/common/snowflake.h | 109 ++++++++++++++++++ hikyuu_cpp/hikyuu_server/http/HttpError.h | 2 +- hikyuu_cpp/hikyuu_server/http/HttpFilter.h | 4 +- hikyuu_cpp/hikyuu_server/http/HttpHandle.cpp | 2 +- .../service/Account/LoginHandle.h | 4 +- .../hikyuu_server/service/RestErrorCode.h | 2 +- hikyuu_cpp/hikyuu_server/service/RestHandle.h | 4 +- .../service/assist/LogLevelHandle.h | 11 +- .../service/trade/TradeAccountHandle.cpp | 21 ++-- .../service/trade/TradeService.cpp | 44 ++++--- .../service/trade/TradeService.h | 16 +++ .../service/trade/db/sqlite/create.cpp | 54 +++++++-- .../service/trade/model/TradeAccountModel.h | 39 +++---- test_data/trader.db | Bin 126976 -> 155648 bytes 15 files changed, 239 insertions(+), 75 deletions(-) create mode 100644 hikyuu_cpp/hikyuu_server/common/snowflake.h diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp index 9e500d80e..f2385158c 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp @@ -92,7 +92,7 @@ bool SQLiteConnect::tableExist(const string& tablename) { } void SQLiteConnect::transaction() { - exec("BEGIN TRANSACTION"); + exec("BEGIN IMMEDIATE"); } void SQLiteConnect::commit() { diff --git a/hikyuu_cpp/hikyuu_server/common/snowflake.h b/hikyuu_cpp/hikyuu_server/common/snowflake.h new file mode 100644 index 000000000..9c0bcc275 --- /dev/null +++ b/hikyuu_cpp/hikyuu_server/common/snowflake.h @@ -0,0 +1,109 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * The code comes from: https://github.com/sniper00/snowflake-cpp + * Thanks sniper00 + * + * Create on: 2021-04-13 + * Author: fasiondog + */ + +#pragma once + +#include +#include +#include +#include + +namespace hku { + +class snowflake_nonlock { +public: + void lock() {} + void unlock() {} +}; + +template +class snowflake { + using lock_type = Lock; + static constexpr int64_t TWEPOCH = Twepoch; + static constexpr int64_t WORKER_ID_BITS = 5L; + static constexpr int64_t DATACENTER_ID_BITS = 5L; + static constexpr int64_t MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1; + static constexpr int64_t MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) - 1; + static constexpr int64_t SEQUENCE_BITS = 12L; + static constexpr int64_t WORKER_ID_SHIFT = SEQUENCE_BITS; + static constexpr int64_t DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; + static constexpr int64_t TIMESTAMP_LEFT_SHIFT = + SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; + static constexpr int64_t SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1; + + using time_point = std::chrono::time_point; + + time_point start_time_point_ = std::chrono::steady_clock::now(); + int64_t start_millsecond_ = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + int64_t last_timestamp_ = -1; + int64_t workerid_ = 0; + int64_t datacenterid_ = 0; + int64_t sequence_ = 0; + lock_type lock_; + +public: + snowflake() = default; + + snowflake(const snowflake&) = delete; + + snowflake& operator=(const snowflake&) = delete; + + void init(int64_t workerid, int64_t datacenterid) { + if (workerid > MAX_WORKER_ID || workerid < 0) { + throw std::runtime_error("worker Id can't be greater than 31 or less than 0"); + } + + if (datacenterid > MAX_DATACENTER_ID || datacenterid < 0) { + throw std::runtime_error("datacenter Id can't be greater than 31 or less than 0"); + } + + workerid_ = workerid; + datacenterid_ = datacenterid; + } + + int64_t nextid() { + std::lock_guard lock(lock_); + // std::chrono::steady_clock cannot decrease as physical time moves forward + auto timestamp = millsecond(); + if (last_timestamp_ == timestamp) { + sequence_ = (sequence_ + 1) & SEQUENCE_MASK; + if (sequence_ == 0) { + timestamp = wait_next_millis(last_timestamp_); + } + } else { + sequence_ = 0; + } + + last_timestamp_ = timestamp; + + return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | + (datacenterid_ << DATACENTER_ID_SHIFT) | (workerid_ << WORKER_ID_SHIFT) | sequence_; + } + +private: + int64_t millsecond() const noexcept { + auto diff = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time_point_); + return start_millsecond_ + diff.count(); + } + + int64_t wait_next_millis(int64_t last) const noexcept { + auto timestamp = millsecond(); + while (timestamp <= last) { + timestamp = millsecond(); + } + return timestamp; + } +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu_server/http/HttpError.h b/hikyuu_cpp/hikyuu_server/http/HttpError.h index 6352a6f1c..e2ba30473 100644 --- a/hikyuu_cpp/hikyuu_server/http/HttpError.h +++ b/hikyuu_cpp/hikyuu_server/http/HttpError.h @@ -30,7 +30,7 @@ enum HttpErrorCode { WRONG_PARAMETER_TYPE // 参数类型错误(各个业务接口返回各个接口的参数) }; -#define HTTP_VALID_CHECK(expr, errcode, ...) \ +#define HTTP_CHECK(expr, errcode, ...) \ { \ if (!(expr)) { \ throw HttpError(errcode, fmt::format(__VA_ARGS__)); \ diff --git a/hikyuu_cpp/hikyuu_server/http/HttpFilter.h b/hikyuu_cpp/hikyuu_server/http/HttpFilter.h index 5cd962664..e77be79b1 100644 --- a/hikyuu_cpp/hikyuu_server/http/HttpFilter.h +++ b/hikyuu_cpp/hikyuu_server/http/HttpFilter.h @@ -16,12 +16,12 @@ inline void MissContentFilter(HttpHandle *handle) { void *data = nullptr; size_t len = 0; handle->getReqData(&data, &len); - HTTP_VALID_CHECK(data, HttpErrorCode::MISS_CONTENT, "Miss content!"); + HTTP_CHECK(data, HttpErrorCode::MISS_CONTENT, "Miss content!"); } inline void ApiTokenAuthorizeFilter(HttpHandle *handle) { const char *token = handle->getReqHeader("token"); - HTTP_VALID_CHECK(token, HttpErrorCode::MISS_TOKEN, "Miss token!"); + HTTP_CHECK(token, HttpErrorCode::MISS_TOKEN, "Miss token!"); } } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu_server/http/HttpHandle.cpp b/hikyuu_cpp/hikyuu_server/http/HttpHandle.cpp index af626d054..3739e0607 100644 --- a/hikyuu_cpp/hikyuu_server/http/HttpHandle.cpp +++ b/hikyuu_cpp/hikyuu_server/http/HttpHandle.cpp @@ -111,7 +111,7 @@ json HttpHandle::getReqJson() { void* data; size_t len; nng_http_req_get_data(m_nng_req, &data, &len); - HTTP_VALID_CHECK(data, INVALID_JSON_REQUEST, "Req data is empty!"); + HTTP_CHECK(data, INVALID_JSON_REQUEST, "Req data is empty!"); json result; try { result = json::parse((const char*)data); diff --git a/hikyuu_cpp/hikyuu_server/service/Account/LoginHandle.h b/hikyuu_cpp/hikyuu_server/service/Account/LoginHandle.h index 84a4fa847..97877640e 100644 --- a/hikyuu_cpp/hikyuu_server/service/Account/LoginHandle.h +++ b/hikyuu_cpp/hikyuu_server/service/Account/LoginHandle.h @@ -16,8 +16,8 @@ class LoginHandle : public HttpHandle { virtual void run() override { json req = getReqJson(); - HTTP_VALID_CHECK(req.contains("user"), HttpErrorCode::MISS_PARAMETER, - "Invalid login request! missing user"); + HTTP_CHECK(req.contains("user"), HttpErrorCode::MISS_PARAMETER, + "Invalid login request! missing user"); setResHeader("Content-Type", "application/json; charset=UTF-8"); setResData( R"({"hku_token":"7c98806c0711cf996d602890e0ab9119d9a86afe04296ba69a16f0d9d76be755"})"); diff --git a/hikyuu_cpp/hikyuu_server/service/RestErrorCode.h b/hikyuu_cpp/hikyuu_server/service/RestErrorCode.h index 92e088762..24bdf3f71 100644 --- a/hikyuu_cpp/hikyuu_server/service/RestErrorCode.h +++ b/hikyuu_cpp/hikyuu_server/service/RestErrorCode.h @@ -9,6 +9,6 @@ namespace hku { -enum TradeErrorCode { TD_ACCOUNT_REPETITION = 20000 }; +enum TradeErrorCode { TD_ACCOUNT_REPETITION = 20000, TD_ACCOUNT_INVALD_TYPE }; } \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu_server/service/RestHandle.h b/hikyuu_cpp/hikyuu_server/service/RestHandle.h index c9bc6f603..a8047d58c 100644 --- a/hikyuu_cpp/hikyuu_server/service/RestHandle.h +++ b/hikyuu_cpp/hikyuu_server/service/RestHandle.h @@ -14,8 +14,8 @@ namespace hku { inline void AuthorizeFilter(HttpHandle *handle) { const char *token = handle->getReqHeader("hku_token"); - HTTP_VALID_CHECK(token, HttpErrorCode::MISS_TOKEN, "Miss token!"); - HTTP_VALID_CHECK( + HTTP_CHECK(token, HttpErrorCode::MISS_TOKEN, "Miss token!"); + HTTP_CHECK( strcmp(token, "7c98806c0711cf996d602890e0ab9119d9a86afe04296ba69a16f0d9d76be755") == 0, HttpErrorCode::UNAUTHORIZED, "Failed authorize!"); } diff --git a/hikyuu_cpp/hikyuu_server/service/assist/LogLevelHandle.h b/hikyuu_cpp/hikyuu_server/service/assist/LogLevelHandle.h index a24178ed2..2ee8634dc 100644 --- a/hikyuu_cpp/hikyuu_server/service/assist/LogLevelHandle.h +++ b/hikyuu_cpp/hikyuu_server/service/assist/LogLevelHandle.h @@ -16,10 +16,9 @@ class LogLevelHandle : public RestHandle { virtual void run() override { json req = getReqJson(); - HTTP_VALID_CHECK(req.contains("level"), HttpErrorCode::MISS_PARAMETER, - "Missing key: level"); - HTTP_VALID_CHECK(req["level"].is_number_integer(), HttpErrorCode::WRONG_PARAMETER_TYPE, - "level type must be integer"); + HTTP_CHECK(req.contains("level"), HttpErrorCode::MISS_PARAMETER, "Missing key: level"); + HTTP_CHECK(req["level"].is_number_integer(), HttpErrorCode::WRONG_PARAMETER_TYPE, + "level type must be integer"); int level = req["level"].get(); if (!req.contains("logger")) { set_logger_level(level); @@ -27,8 +26,8 @@ class LogLevelHandle : public RestHandle { return; } - HTTP_VALID_CHECK(req["logger"].is_string(), HttpErrorCode::WRONG_PARAMETER_TYPE, - "logger type must be string"); + HTTP_CHECK(req["logger"].is_string(), HttpErrorCode::WRONG_PARAMETER_TYPE, + "logger type must be string"); std::string logger = req["logger"].get(); if (have_logger(logger)) { set_logger_level(logger, level); diff --git a/hikyuu_cpp/hikyuu_server/service/trade/TradeAccountHandle.cpp b/hikyuu_cpp/hikyuu_server/service/trade/TradeAccountHandle.cpp index 176d03037..b152e5812 100644 --- a/hikyuu_cpp/hikyuu_server/service/trade/TradeAccountHandle.cpp +++ b/hikyuu_cpp/hikyuu_server/service/trade/TradeAccountHandle.cpp @@ -13,25 +13,24 @@ namespace hku { void AddTradeAccountHandle::run() { json req = getReqJson(); - HTTP_VALID_CHECK(req.contains("name"), HttpErrorCode::MISS_PARAMETER, - R"(Missing param "name")"); - HTTP_VALID_CHECK(req.contains("type"), HttpErrorCode::MISS_PARAMETER, - R"(Missing param "type")"); + HTTP_CHECK(req.contains("name"), HttpErrorCode::MISS_PARAMETER, R"(Missing param "name")"); + HTTP_CHECK(req.contains("type"), HttpErrorCode::MISS_PARAMETER, R"(Missing param "type")"); TradeAccountModel account; std::string name = req["name"].get(); + std::string td_type = req["type"].get(); + HTTP_CHECK(TradeService::isValidEumValue(TradeAccountModel::getTableName(), "type", td_type), + TradeErrorCode::TD_ACCOUNT_INVALD_TYPE, "Invalid trade account type: {}", td_type); account.setName(name); - account.setType(req["type"].get()); - account.setAccount(UUID()); + account.setType(td_type); + account.setTdId(TradeService::newTdId()); auto con = TradeService::getDBConnect(); { TransAction trans(con); - HTTP_VALID_CHECK(!TradeAccountModel::isExistName(con, name), - TradeErrorCode::TD_ACCOUNT_REPETITION, "Name repetition"); + HTTP_CHECK(!TradeAccountModel::isExistName(con, name), + TradeErrorCode::TD_ACCOUNT_REPETITION, "Name repetition"); con->save(account, false); } - json res; - to_json(res, account); - setResData(res); + setResData(account.json()); } void GetTradeAccountHandle::run() {} diff --git a/hikyuu_cpp/hikyuu_server/service/trade/TradeService.cpp b/hikyuu_cpp/hikyuu_server/service/trade/TradeService.cpp index dd0f76bd0..0503ab2d5 100644 --- a/hikyuu_cpp/hikyuu_server/service/trade/TradeService.cpp +++ b/hikyuu_cpp/hikyuu_server/service/trade/TradeService.cpp @@ -15,6 +15,29 @@ namespace hku { std::unique_ptr> TradeService::ms_sqlite_pool; std::unique_ptr> TradeService::ms_mysql_pool; +TradeService::snowflake_t TradeService::ms_td_id_generator; +TradeService::snowflake_t TradeService::ms_sta_id_generator; + +TradeService::TradeService(const char* url, const std::string& config_file) : HttpService(url) { + ms_td_id_generator.init(1, 1); + ms_sta_id_generator.init(1, 1); + + if (ms_sqlite_pool || ms_mysql_pool) { + return; + } + + IniParser ini; + ini.read(config_file); + + Parameter param; + auto options = ini.getOptionList("database"); + for (auto& option : *options) { + param.set(option, ini.get("database", option)); + } + + initTradeServiceDB(param); +} + void TradeService::initTradeServiceSqlite(const Parameter& param) { Parameter sqlite_param; sqlite_param.set("db", param.get("db")); @@ -55,21 +78,12 @@ DBConnectPtr TradeService::getDBConnect() { : DBConnectPtr(); } -TradeService::TradeService(const char* url, const std::string& config_file) : HttpService(url) { - if (ms_sqlite_pool || ms_mysql_pool) { - return; - } - - IniParser ini; - ini.read(config_file); - - Parameter param; - auto options = ini.getOptionList("database"); - for (auto& option : *options) { - param.set(option, ini.get("database", option)); - } - - initTradeServiceDB(param); +bool TradeService::isValidEumValue(const std::string& table, const std::string& field, + const std::string& val) { + int count = getDBConnect()->queryInt(fmt::format( + R"(select count(1) from td_enum where table_name="{}" and field_name="{}" and value="{}")", + table, field, val)); + return count > 0; } } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu_server/service/trade/TradeService.h b/hikyuu_cpp/hikyuu_server/service/trade/TradeService.h index d61d3b6a7..53f520bc2 100644 --- a/hikyuu_cpp/hikyuu_server/service/trade/TradeService.h +++ b/hikyuu_cpp/hikyuu_server/service/trade/TradeService.h @@ -12,6 +12,7 @@ #include #include #include "http/HttpService.h" +#include "common/snowflake.h" #include "WalletHandle.h" #include "TradeAccountHandle.h" @@ -38,6 +39,17 @@ class TradeService : public HttpService { public: static DBConnectPtr getDBConnect(); + static int64_t newTdId() { + return ms_td_id_generator.nextid(); + } + + static int64_t newStaId() { + return ms_sta_id_generator.nextid(); + } + + static bool isValidEumValue(const std::string &table, const std::string &field, + const std::string &val); + private: static void initTradeServiceSqlite(const Parameter ¶m); static void initTradeServiceMysql(const Parameter ¶m); @@ -46,6 +58,10 @@ class TradeService : public HttpService { private: static std::unique_ptr> ms_sqlite_pool; static std::unique_ptr> ms_mysql_pool; + + using snowflake_t = snowflake<1618243200000L, std::mutex>; + static snowflake_t ms_td_id_generator; + static snowflake_t ms_sta_id_generator; }; } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu_server/service/trade/db/sqlite/create.cpp b/hikyuu_cpp/hikyuu_server/service/trade/db/sqlite/create.cpp index 8fddecca0..fdab2c8fa 100644 --- a/hikyuu_cpp/hikyuu_server/service/trade/db/sqlite/create.cpp +++ b/hikyuu_cpp/hikyuu_server/service/trade/db/sqlite/create.cpp @@ -13,16 +13,16 @@ const char *g_sqlite_create_db{ R"( CREATE TABLE "td_account" ( "id" INTEGER NOT NULL UNIQUE, - "account" TEXT NOT NULL UNIQUE, + "td_id" INTEGER NOT NULL UNIQUE, "name" TEXT NOT NULL UNIQUE, "type" TEXT NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); -CREATE UNIQUE INDEX "ix_td_account_on_account" ON "td_account" ( - "account" ASC +CREATE UNIQUE INDEX "ix_td_account_on_td_id" ON "td_account" ( + "td_id" ); CREATE UNIQUE INDEX "ix_td_account_on_name" ON "td_account" ( - "name" ASC + "name" ); CREATE TABLE "td_funds" ( "id" INTEGER NOT NULL UNIQUE, @@ -33,14 +33,22 @@ CREATE TABLE "td_funds" ( "available" REAL NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_funds_on_td_id" ON "td_funds" ( + "td_id" +); CREATE TABLE "td_enum" ( "id" INTEGER NOT NULL UNIQUE, - "table" TEXT NOT NULL, - "field" TEXT NOT NULL, - "value" INTEGER NOT NULL, - "string" TEXT NOT NULL, + "table_name" TEXT NOT NULL, + "field_name" TEXT NOT NULL, + "value" TEXT NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_enum_on_tabel_field" ON "td_enum" ( + "table_name", + "field_name", + "value" +); +INSERT INTO "td_enum" ("id", "table_name", "field_name", "value") VALUES ('1', 'td_account', 'type', 'xq'); CREATE TABLE "td_positions" ( "id" INTEGER NOT NULL UNIQUE, "td_id" INTEGER NOT NULL, @@ -58,6 +66,10 @@ CREATE TABLE "td_positions" ( "ref_profit" REAL NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_positions_on_td_id_sta_id" ON "td_positions" ( + "td_id", + "sta_id" +); CREATE TABLE "td_orders" ( "id" INTEGER NOT NULL UNIQUE, "td_id" INTEGER NOT NULL, @@ -74,6 +86,13 @@ CREATE TABLE "td_orders" ( "status" INTEGER NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_orders_on_td_id_sta_id" ON "td_orders" ( + "td_id", + "sta_id" +); +CREATE INDEX "ix_td_orders_on_datetime" ON "td_orders" ( + "datetime" ASC +); CREATE TABLE "td_fills" ( "id" INTEGER NOT NULL UNIQUE, "td_id" INTEGER NOT NULL, @@ -87,17 +106,24 @@ CREATE TABLE "td_fills" ( "order_seq" TEXT NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_fills_on_td_id_sta_id" ON "td_fills" ( + "td_id", + "sta_id" +); +CREATE INDEX "ix_td_fills_on_datetime" ON "td_fills" ( + "datetime" ASC +); CREATE TABLE "td_sta_account" ( "id" INTEGER NOT NULL UNIQUE, + "sta_id" INTEGER NOT NULL UNIQUE, "td_id" INTEGER NOT NULL, - "account" TEXT NOT NULL, "name" TEXT NOT NULL, "path" TEXT NOT NULL, "status" TEXT NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); -CREATE UNIQUE INDEX "ix_td_sta_account_on_account" ON "td_sta_account" ( - "account" ASC +CREATE UNIQUE INDEX "ix_td_sta_account_on_sta_id" ON "td_sta_account" ( + "sta_id" ASC ); CREATE TABLE "td_sta_funds" ( "id" INTEGER NOT NULL UNIQUE, @@ -108,6 +134,9 @@ CREATE TABLE "td_sta_funds" ( "available" REAL NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_sta_funds_on_sta_id" ON "td_sta_funds" ( + "sta_id" +); CREATE TABLE "td_account_xq" ( "id" INTEGER NOT NULL UNIQUE, "td_id" INTEGER NOT NULL, @@ -116,6 +145,9 @@ CREATE TABLE "td_account_xq" ( "portfolio_market" TEXT NOT NULL, PRIMARY KEY("id" AUTOINCREMENT) ); +CREATE INDEX "ix_td_account_xq_on_td_id" ON "td_account_xq" ( + "td_id" ASC +); )"}; diff --git a/hikyuu_cpp/hikyuu_server/service/trade/model/TradeAccountModel.h b/hikyuu_cpp/hikyuu_server/service/trade/model/TradeAccountModel.h index 891a5ed10..ae1fe5e17 100644 --- a/hikyuu_cpp/hikyuu_server/service/trade/model/TradeAccountModel.h +++ b/hikyuu_cpp/hikyuu_server/service/trade/model/TradeAccountModel.h @@ -7,15 +7,12 @@ #pragma once -#include #include -using nlohmann::json; - namespace hku { class TradeAccountModel { - TABLE_BIND3(td_account, account, name, type) + TABLE_BIND3(td_account, td_id, name, type) public: static bool isExistName(DBConnectPtr con, const std::string& name) { @@ -29,12 +26,12 @@ class TradeAccountModel { } public: - std::string getAccount() const { - return account; + int64_t getTdId() const { + return td_id; } - void setAccount(const std::string& account) { - this->account = account; + void setTdId(int64_t id) { + td_id = id; } std::string getName() const { @@ -53,23 +50,21 @@ class TradeAccountModel { this->type = type; } -private: - string account; - string name; - string type; + string json() const { + return fmt::format(R"({{"td_id":"{}", "name":"{}", "type":"{}"}})", td_id, name, type); + } - friend void to_json(json& j, const TradeAccountModel& p); - friend void from_json(const json& j, TradeAccountModel& p); +private: + int64_t td_id; // 内部交易账户id + string name; // 内部交易账户名称 + string type; // 内部交易账户类型:xq(雪球模拟账户) }; -inline void to_json(json& j, const TradeAccountModel& p) { - j = json{{"account", p.account}, {"name", p.name}, {"type", p.type}}; -} - -inline void from_json(const json& j, TradeAccountModel& p) { - j.at("account").get_to(p.account); - j.at("name").get_to(p.name); - j.at("type").get_to(p.type); +inline std::ostream& operator<<(std::ostream& out, const TradeAccountModel& model) { + string strip(", "); + out << "(" << model.id() << strip << model.getTdId() << strip << model.getName() << strip + << model.getType() << ")"; + return out; } } // namespace hku \ No newline at end of file diff --git a/test_data/trader.db b/test_data/trader.db index 00d550412fc44545c7652e0d05afb8e6bacf1bca..61ffc765b2c5eff26237f3184c7e2aa6260eff3b 100644 GIT binary patch delta 1625 zcmZ`(OK;Oy6!uBl+}I{B72(pf#4)7Q&>5&I1fr@!l_?{H=woZbK}7=#1fmpFi4EPM5o{0}L}BO;0KsB*GtzKrJctXMTaNa;r@zg71SxLgL4uq2q2O^mPF4(8>jM zR-0Ayf>P8e`fAw{_-z85}q=C>|L4Y4BH3`cAYY4iAY^Jx3C|I994x zlGuU6IZnQWw<95&0b>nrRnIHM>2iT$+b>#yRB^2tKXlh?^L27ueAnl2sChXV2kXuqovD%^w17Mx#g^r;%_l1GwKax^tDPrfZcl zZGjZj9R9Qs*SFcP5!Y)s;>~p1u_Fvx-06n~N!?_+4N^^5M8aSSaF2&nW-jj*z)kU&&g--CM4`8 zP!TUwUBd2asi;L?thKQXFx&o(bc~C*ohUa6IO9Q6%hOYIZTWGvM6QoovW06~+x-B2 z!d+0?SL-i7UQ+3LXY!?KRpMj=C-)HWt!u$+dR1o6yUHQp4QmUfU`77~q0bZJA~qtu zb9L@1W^fx4`qO}SSn)S-+B&%c3Dk>QL&DE#YheYF*8MjSHot^;NG;`W!J$A4AC1m1 zA%?**EE6q~Th>o5+2wdmq)y>J++{s{0PW`gY%17?x6&8u=4}wU816)ZEW@?q)?c?_ z!ust4v|1nTL8o=&9ZVXNxWh+T^%c6xvJD-6!*@S!G}N5}HG2oZ$-Q`^-6k+})7A4O zO;;7`*$PCQrsLstJRZRU64Gh7de(aQ9yUVAOr!l~4kb+k4OugPLr+T&NgY>ob?Tg= j+dEZyH!RKFf&Fl272@+F-##}+cs4`6?_0=mzWrYTU=ao_e_{X+Bhz*c2S#Rv?c5EFKjpWx zG%&I|Z02_O#;?iqlYx(em4WXI-!ASK+^e{AxNdO%<~+@DoTHm}DQ`ZnB?l{C8T&8x zUqB04*y}l(7|yVZ>+3T%*_R|H<)juD<|M_Jq~;dHgSqjB7y>fRL9UJ=t_mTJPCl** zN>F*8pNygqb5gOIV}xoB(3Q_X%99zJim|FUMpd60Z!C<}UbsVzl@v6%IF&L}lsG;8 zLR=e}8pYVfZ50_CjVC8CsZUm5=a|lYgV9o$7pN#PIXS;HuSAK{G1!@xOLKeT0>(TR ze!emWhen@9K6Y_MMaHJ&>1*#YO2{}iim-`?>N7HA=B1=o03DTCk{X{_T9R+vC^|j= zE~62PtZ}0Vh^0RH0~}#f%-D5NqM)Nz+FS6SwEMs(IT=sxb F007K0tCIi#