修复: 升级框架并完善报告导出

- 升级 Drogon 和 Trantor,修复畸形请求导致的连接计数泄漏\n- 增加第三方框架版本校验与自动重建\n- 完善完整报告导出和接口文档
This commit is contained in:
cloud
2026-08-10 09:50:09 +08:00
parent 99ed321d24
commit 0e28826073
82 changed files with 3095 additions and 566 deletions
+13 -1
View File
@@ -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> &parameters)
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>;
+3
View File
@@ -116,3 +116,6 @@ void create_filter::handleCommand(std::vector<std::string> &parameters)
createFilterSourceFile(oSourceFile, className, fileName);
}
}
// See create.cc for rationale.
template class drogon::DrObject<drogon_ctl::create_filter>;
+195 -3
View File
@@ -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> &parameters)
++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>;
+1
View File
@@ -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
+3
View File
@@ -116,3 +116,6 @@ void create_plugin::handleCommand(std::vector<std::string> &parameters)
createPluginSourceFile(oSourceFile, className, fileName);
}
}
// See create.cc for rationale.
template class drogon::DrObject<drogon_ctl::create_plugin>;
+3
View File
@@ -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>;
+3
View File
@@ -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
View File
@@ -68,3 +68,6 @@ void help::handleCommand(std::vector<std::string> &parameters)
}
}
}
// See create.cc for rationale.
template class drogon::DrObject<drogon_ctl::help>;
+3
View File
@@ -470,3 +470,6 @@ void press::outputResults()
<< std::endl;
exit(0);
}
// See create.cc for rationale.
template class drogon::DrObject<drogon_ctl::press>;
+3
View File
@@ -66,3 +66,6 @@ void version::handleCommand(std::vector<std::string> &parameters)
std::cout << " yaml-cpp: no\n";
#endif
}
// See create.cc for rationale.
template class drogon::DrObject<drogon_ctl::version>;