修复: 升级框架并完善报告导出
- 升级 Drogon 和 Trantor,修复畸形请求导致的连接计数泄漏\n- 增加第三方框架版本校验与自动重建\n- 完善完整报告导出和接口文档
This commit is contained in:
+13
-1
@@ -42,7 +42,8 @@ std::string create::detail()
|
||||
"create a plugin named class_name\n\n"
|
||||
"drogon_ctl create project <project_name> //"
|
||||
"create a project named project_name\n\n"
|
||||
"drogon_ctl create model <model_path> [-o <output path>] "
|
||||
"drogon_ctl create model <model_path> [-o <output path>] [ "
|
||||
"--clear-output]"
|
||||
"[--table=<table_name>] [-f]//"
|
||||
"create model classes in model_path\n";
|
||||
}
|
||||
@@ -55,3 +56,14 @@ void create::handleCommand(std::vector<std::string> ¶meters)
|
||||
parameters[0] = createObjName;
|
||||
exeCommand(parameters);
|
||||
}
|
||||
|
||||
// Prevent clang-cl/lld-link from discarding DrObject<T>::alloc_ on Windows.
|
||||
// On COFF targets, clang places the CRT initializer for the template static
|
||||
// member in the same COMDAT group as the variable itself. When no code in the
|
||||
// translation unit takes the address of alloc_ (clang inlines className()),
|
||||
// the entire COMDAT is eligible for elimination — and lld-link's /OPT:REF
|
||||
// removes it, so DrClassMap is never populated.
|
||||
// Explicit template instantiation forces a strong (non-COMDAT) definition,
|
||||
// which the linker must keep. This is a no-op on MSVC, GCC, and ELF targets
|
||||
// (where .init_array entries are GC roots and are never discarded).
|
||||
template class drogon::DrObject<drogon_ctl::create>;
|
||||
|
||||
@@ -469,3 +469,6 @@ void create_controller::createARestfulController(const std::string &className,
|
||||
std::cout << "File name: " << ctlName << ".h and " << ctlName << ".cc"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::create_controller>;
|
||||
|
||||
@@ -116,3 +116,6 @@ void create_filter::handleCommand(std::vector<std::string> ¶meters)
|
||||
createFilterSourceFile(oSourceFile, className, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::create_filter>;
|
||||
|
||||
+195
-3
@@ -164,6 +164,59 @@ bool drogon_ctl::ConvertMethod::shouldConvert(const std::string &tableName,
|
||||
} // endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to add an auto-detected FK relationship to the list.
|
||||
*
|
||||
* Checks for duplicates against existing relationships, creates a
|
||||
* Relationship object from the FK info, and appends it to the list.
|
||||
* User-configured relationships always take priority.
|
||||
*
|
||||
* @param allRelationships The mutable vector of relationships.
|
||||
* @param originalTable The table containing the FK column.
|
||||
* @param fkColumn The FK column name.
|
||||
* @param referencedTable The table referenced by the FK.
|
||||
* @param referencedColumn The column referenced by the FK.
|
||||
* @param normalizeNames If true, apply toLower() to table names.
|
||||
*/
|
||||
static void tryAddAutoRelationship(std::vector<Relationship> &allRelationships,
|
||||
const std::string &originalTable,
|
||||
const std::string &fkColumn,
|
||||
const std::string &referencedTable,
|
||||
const std::string &referencedColumn,
|
||||
bool normalizeNames)
|
||||
{
|
||||
for (const auto &r : allRelationships)
|
||||
{
|
||||
if (r.originalKey() == fkColumn &&
|
||||
r.targetTableName() == referencedTable)
|
||||
{
|
||||
return; // Already exists in user config
|
||||
}
|
||||
}
|
||||
Json::Value relJson;
|
||||
relJson["type"] = "has one";
|
||||
relJson["original_table_name"] =
|
||||
normalizeNames ? toLower(originalTable) : originalTable;
|
||||
relJson["original_key"] = fkColumn;
|
||||
relJson["target_table_name"] =
|
||||
normalizeNames ? toLower(referencedTable) : referencedTable;
|
||||
relJson["target_key"] = referencedColumn;
|
||||
relJson["enable_reverse"] = true;
|
||||
try
|
||||
{
|
||||
Relationship autoRel(relJson);
|
||||
allRelationships.push_back(autoRel);
|
||||
std::cout << " Auto-detected FK: " << originalTable << "."
|
||||
<< fkColumn << " -> " << referencedTable << "."
|
||||
<< referencedColumn << std::endl;
|
||||
}
|
||||
catch (const std::runtime_error &e)
|
||||
{
|
||||
std::cerr << "Warning: Could not create auto-relationship: " << e.what()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_POSTGRESQL
|
||||
void create_model::createModelClassFromPG(
|
||||
const std::string &path,
|
||||
@@ -182,8 +235,9 @@ void create_model::createModelClassFromPG(
|
||||
data["primaryKeyName"] = "";
|
||||
data["dbName"] = dbname_;
|
||||
data["rdbms"] = std::string("postgresql");
|
||||
data["relationships"] = relationships;
|
||||
data["convertMethods"] = convertMethods;
|
||||
// Start with user-configured relationships (mutable copy)
|
||||
std::vector<Relationship> allRelationships(relationships);
|
||||
if (schema != "public")
|
||||
{
|
||||
data["schema"] = schema;
|
||||
@@ -397,6 +451,42 @@ void create_model::createModelClassFromPG(
|
||||
data["primaryKeyValNames"] = pkValNames;
|
||||
}
|
||||
|
||||
// Auto-detect foreign key relationships from database schema
|
||||
*client << "SELECT "
|
||||
"kcu.column_name AS fk_column, "
|
||||
"ccu.table_name AS referenced_table, "
|
||||
"ccu.column_name AS referenced_column "
|
||||
"FROM information_schema.key_column_usage kcu "
|
||||
"JOIN information_schema.referential_constraints rc "
|
||||
"ON kcu.constraint_name = rc.constraint_name "
|
||||
"AND kcu.constraint_schema = rc.constraint_schema "
|
||||
"JOIN information_schema.constraint_column_usage ccu "
|
||||
"ON rc.unique_constraint_name = ccu.constraint_name "
|
||||
"AND rc.unique_constraint_schema = ccu.constraint_schema "
|
||||
"WHERE kcu.table_name = $1 "
|
||||
"AND kcu.table_schema = $2"
|
||||
<< tableName << schema << Mode::Blocking >>
|
||||
[&](bool isNull,
|
||||
const std::string &fkColumn,
|
||||
const std::string &referencedTable,
|
||||
const std::string &referencedColumn) {
|
||||
if (!isNull)
|
||||
{
|
||||
tryAddAutoRelationship(allRelationships,
|
||||
tableName,
|
||||
fkColumn,
|
||||
referencedTable,
|
||||
referencedColumn,
|
||||
true);
|
||||
}
|
||||
} >>
|
||||
[](const DrogonDbException &e) {
|
||||
// FK detection is best-effort; don't fail if unsupported
|
||||
std::cerr << "Note: FK auto-detection not available: "
|
||||
<< e.base().what() << std::endl;
|
||||
};
|
||||
|
||||
data["relationships"] = allRelationships;
|
||||
data["columns"] = cols;
|
||||
std::ofstream headerFile(path + "/" + className + ".h", std::ofstream::out);
|
||||
std::ofstream sourceFile(path + "/" + className + ".cc",
|
||||
@@ -467,8 +557,9 @@ void create_model::createModelClassFromMysql(
|
||||
data["primaryKeyName"] = "";
|
||||
data["dbName"] = dbname_;
|
||||
data["rdbms"] = std::string("mysql");
|
||||
data["relationships"] = relationships;
|
||||
data["convertMethods"] = convertMethods;
|
||||
// Start with user-configured relationships (mutable copy)
|
||||
std::vector<Relationship> allRelationships(relationships);
|
||||
std::vector<ColumnInfo> cols;
|
||||
int i = 0;
|
||||
*client << "desc `" + tableName + "`" << Mode::Blocking >>
|
||||
@@ -593,6 +684,35 @@ void create_model::createModelClassFromMysql(
|
||||
data["primaryKeyType"] = pkTypes;
|
||||
data["primaryKeyValNames"] = pkValNames;
|
||||
}
|
||||
|
||||
// Auto-detect foreign key relationships from MySQL schema
|
||||
*client << "SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, "
|
||||
"REFERENCED_COLUMN_NAME "
|
||||
"FROM information_schema.KEY_COLUMN_USAGE "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() "
|
||||
"AND TABLE_NAME = ? "
|
||||
"AND REFERENCED_TABLE_NAME IS NOT NULL"
|
||||
<< tableName << Mode::Blocking >>
|
||||
[&](bool isNull,
|
||||
const std::string &fkColumn,
|
||||
const std::string &referencedTable,
|
||||
const std::string &referencedColumn) {
|
||||
if (!isNull)
|
||||
{
|
||||
tryAddAutoRelationship(allRelationships,
|
||||
tableName,
|
||||
fkColumn,
|
||||
referencedTable,
|
||||
referencedColumn,
|
||||
true);
|
||||
}
|
||||
} >>
|
||||
[](const DrogonDbException &e) {
|
||||
std::cerr << "Note: FK auto-detection not available: "
|
||||
<< e.base().what() << std::endl;
|
||||
};
|
||||
|
||||
data["relationships"] = allRelationships;
|
||||
data["columns"] = cols;
|
||||
std::ofstream headerFile(path + "/" + className + ".h", std::ofstream::out);
|
||||
std::ofstream sourceFile(path + "/" + className + ".cc",
|
||||
@@ -646,8 +766,9 @@ void create_model::createModelClassFromSqlite3(
|
||||
data["primaryKeyName"] = "";
|
||||
data["dbName"] = std::string("sqlite3");
|
||||
data["rdbms"] = std::string("sqlite3");
|
||||
data["relationships"] = relationships;
|
||||
data["convertMethods"] = convertMethods;
|
||||
// Start with user-configured relationships (mutable copy)
|
||||
std::vector<Relationship> allRelationships(relationships);
|
||||
std::vector<ColumnInfo> cols;
|
||||
std::string sql = "PRAGMA table_info(" + tableName + ");";
|
||||
*client << sql << Mode::Blocking >> [&](const Result &result) {
|
||||
@@ -774,6 +895,28 @@ void create_model::createModelClassFromSqlite3(
|
||||
data["primaryKeyType"] = pkTypes;
|
||||
data["primaryKeyValNames"] = pkValNames;
|
||||
}
|
||||
|
||||
// Auto-detect foreign key relationships from SQLite3 schema
|
||||
std::string fkSql = "PRAGMA foreign_key_list(\"" + tableName + "\");";
|
||||
*client << fkSql << Mode::Blocking >> [&](const Result &fkResult) {
|
||||
for (auto &fkRow : fkResult)
|
||||
{
|
||||
auto referencedTable = fkRow["table"].as<std::string>();
|
||||
auto fkColumn = fkRow["from"].as<std::string>();
|
||||
auto referencedColumn = fkRow["to"].as<std::string>();
|
||||
tryAddAutoRelationship(allRelationships,
|
||||
tableName,
|
||||
fkColumn,
|
||||
referencedTable,
|
||||
referencedColumn,
|
||||
true);
|
||||
}
|
||||
} >> [](const DrogonDbException &e) {
|
||||
std::cerr << "Note: FK auto-detection not available: "
|
||||
<< e.base().what() << std::endl;
|
||||
};
|
||||
|
||||
data["relationships"] = allRelationships;
|
||||
data["columns"] = cols;
|
||||
std::ofstream headerFile(path + "/" + className + ".h", std::ofstream::out);
|
||||
std::ofstream sourceFile(path + "/" + className + ".cc",
|
||||
@@ -826,7 +969,42 @@ void create_model::createModel(const std::string &path,
|
||||
auto restfulApiConfig = config["restful_api_controllers"];
|
||||
auto relationships = getRelationships(config["relationships"]);
|
||||
auto convertMethods = getConvertMethods(config["convert"]);
|
||||
|
||||
drogon::utils::createPath(path);
|
||||
|
||||
if (cleanupDirectory_)
|
||||
{
|
||||
std::cout << "Source files (*.h, *.cc) in '" << path
|
||||
<< "' folder will be deleted, continue(y/n)?\n";
|
||||
auto in = getchar();
|
||||
(void)getchar(); // get the return key
|
||||
if (in != 'Y' && in != 'y')
|
||||
{
|
||||
std::cout << "Abort!" << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
for (const auto &entry : std::filesystem::directory_iterator(path))
|
||||
{
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
|
||||
const std::filesystem::path &file = entry.path();
|
||||
std::string ext = file.extension().string();
|
||||
|
||||
if (ext == ".h" || ext == ".cc")
|
||||
{
|
||||
std::cout << "Removing: " << file << "\n";
|
||||
std::error_code ret;
|
||||
std::filesystem::remove(file, ret);
|
||||
if (ret)
|
||||
{
|
||||
std::cerr << "Failed to remove '" << file
|
||||
<< "' : " << ret.message() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dbType == "postgresql")
|
||||
{
|
||||
#if USE_POSTGRESQL
|
||||
@@ -1230,6 +1408,17 @@ void create_model::handleCommand(std::vector<std::string> ¶meters)
|
||||
++iter;
|
||||
}
|
||||
|
||||
for (auto iter = parameters.begin(); iter != parameters.end(); ++iter)
|
||||
{
|
||||
if ((*iter) == "--clear-output")
|
||||
{
|
||||
cleanupDirectory_ = true;
|
||||
forceOverwrite_ = true;
|
||||
parameters.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const &path : parameters)
|
||||
{
|
||||
createModel(path, singleModelName);
|
||||
@@ -1414,3 +1603,6 @@ void create_model::createRestfulAPIController(
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::create_model>;
|
||||
|
||||
@@ -430,5 +430,6 @@ class create_model : public DrObject<create_model>, public CommandHandler
|
||||
std::string dbname_;
|
||||
bool forceOverwrite_{false};
|
||||
std::string outputPath_;
|
||||
bool cleanupDirectory_{false};
|
||||
};
|
||||
} // namespace drogon_ctl
|
||||
|
||||
@@ -116,3 +116,6 @@ void create_plugin::handleCommand(std::vector<std::string> ¶meters)
|
||||
createPluginSourceFile(oSourceFile, className, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::create_plugin>;
|
||||
|
||||
@@ -141,3 +141,6 @@ void create_project::createProject(const std::string &projectName)
|
||||
std::ofstream testCmakeFile("test/CMakeLists.txt", std::ofstream::out);
|
||||
newTestCmakeFile(testCmakeFile, projectName);
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::create_project>;
|
||||
|
||||
@@ -552,3 +552,6 @@ void create_view::newViewSourceFile(std::ofstream &file,
|
||||
file << "return templ->genText(data);\n";
|
||||
file << "}\n}\n";
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::create_view>;
|
||||
|
||||
+3
@@ -68,3 +68,6 @@ void help::handleCommand(std::vector<std::string> ¶meters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::help>;
|
||||
|
||||
+3
@@ -470,3 +470,6 @@ void press::outputResults()
|
||||
<< std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::press>;
|
||||
|
||||
@@ -66,3 +66,6 @@ void version::handleCommand(std::vector<std::string> ¶meters)
|
||||
std::cout << " yaml-cpp: no\n";
|
||||
#endif
|
||||
}
|
||||
|
||||
// See create.cc for rationale.
|
||||
template class drogon::DrObject<drogon_ctl::version>;
|
||||
|
||||
Reference in New Issue
Block a user