优化Shrinkwrap接口 - 修复文件名权限问题并简化执行逻辑

- 新增安全文件名生成函数,基于源模型名清理非法字符
- 移除不必要的递归组件分析和差异计算逻辑
- 简化ExecuteShrinkwrapShell方法,直接调用顶层装配体API
- 解决output_file_path路径权限问题,自动保存到当前工作目录
- 更新文档记录核心优化内容

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-17 16:59:03 +08:00
parent 45f455e0c2
commit e3b15ef745
3 changed files with 129 additions and 380 deletions

View File

@ -107,6 +107,9 @@ POST /api/creo/shrinkwrap/shell # Shrinkwrap导出支持动态超
- **细分异常处理**: 区分OTK工具包错误、参数错误、内存不足等
- **向后兼容**: 新参数可选不影响现有API调用
- **详细错误信息**: 为不同异常类型提供具体错误描述和解决建议
- **安全文件名生成**: 基于源模型名生成符合Creo规范的Part名称清理非法字符
- **简化执行逻辑**: 移除不必要的递归组件分析直接调用Creo原生Shrinkwrap API
- **路径问题解决**: 不再依赖用户指定路径,自动保存到当前工作目录避免权限问题
### 几何复杂度分析优化
- **装配体去重机制**: 使用std::set跟踪已处理零件避免重复分析相同零件
@ -155,6 +158,8 @@ POST /api/creo/shrinkwrap/shell # Shrinkwrap导出支持动态超
- 层级统计接口编码问题 → 所有注释改为英文避免UTF-8编码冲突
- 薄壳化特征归因不准确 → LOO算法单独测量Top-K特征
- 缓存脏读问题 → 缓存键加入模型版本和单位系统
- Shrinkwrap文件名权限问题 → 安全文件名生成函数,移除路径依赖
- Shrinkwrap不必要递归循环 → 简化逻辑直接调用顶层装配体API
## 下一步计划

View File

@ -83,12 +83,73 @@ xstring ShrinkwrapManager::StringToXString(const std::string& str) {
std::string ShrinkwrapManager::GetCurrentTimeString() {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%dT%H:%M:%S");
return ss.str();
}
std::string ShrinkwrapManager::GenerateSafePartName(pfcModel_ptr source_model) {
if (!source_model) {
// Fallback to timestamp-based name
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << "shrinkwrap_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
return ss.str();
}
try {
// Get source model name
xstring name_xstr = source_model->GetFileName();
std::string base_name = StringToStdString(name_xstr);
// Remove extension
size_t dot_pos = base_name.find_last_of(".");
if (dot_pos != std::string::npos) {
base_name = base_name.substr(0, dot_pos);
}
// Clean non-ASCII characters, keep only alphanumeric and underscore
std::string safe_name;
safe_name.reserve(base_name.length());
for (char c : base_name) {
if (std::isalnum(static_cast<unsigned char>(c)) || c == '_') {
safe_name += c;
} else if (!safe_name.empty() && safe_name.back() != '_') {
safe_name += '_';
}
}
// Remove trailing underscores
while (!safe_name.empty() && safe_name.back() == '_') {
safe_name.pop_back();
}
// Ensure name is not empty
if (safe_name.empty()) {
safe_name = "model";
}
// Limit length (Creo has 31 character limit, reserve space for suffix)
if (safe_name.length() > 20) {
safe_name = safe_name.substr(0, 20);
}
// Add shrinkwrap suffix
return safe_name + "_shrink";
} catch (...) {
// Fallback to timestamp-based name
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << "shrinkwrap_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S");
return ss.str();
}
}
std::string ShrinkwrapManager::CalculateFileSize(const std::string& file_path) {
try {
HANDLE hFile = CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
@ -126,12 +187,7 @@ bool ShrinkwrapManager::ValidateRequest(const ShrinkwrapShellRequest& request, s
error_message = "Small surface percentage must be between 0.0 and 100.0";
return false;
}
if (request.output_file_path.empty()) {
error_message = "Output file path is required";
return false;
}
return true;
}
@ -153,7 +209,7 @@ void ShrinkwrapManager::ApplyPresetParameters(ShrinkwrapShellRequest& request) {
request.small_surface_percentage = 0.0; // 不过滤
request.fill_holes = false; // 不填孔
request.output_type = "solid_surface";
} else if (request.preset == "balanced") {
// Balanced档平衡设置适合大多数情况
request.quality = 5;
@ -162,7 +218,7 @@ void ShrinkwrapManager::ApplyPresetParameters(ShrinkwrapShellRequest& request) {
request.small_surface_percentage = 0.0;
request.fill_holes = false; // 暂时不填孔
request.output_type = "solid_surface";
} else if (request.preset == "tight") {
// Tight档相对高精度但仍然保守
request.quality = 5; // 限制在5以内避免卡死
@ -175,119 +231,6 @@ void ShrinkwrapManager::ApplyPresetParameters(ShrinkwrapShellRequest& request) {
// 空字符串或其他值保持默认参数
}
std::vector<ShrinkwrapComponentInfo> ShrinkwrapManager::AnalyzeAssemblyComponents(pfcModel_ptr model) {
std::vector<ShrinkwrapComponentInfo> components;
if (!model) {
return components;
}
try {
if (model->GetType() == pfcMDL_ASSEMBLY) {
wfcWAssembly_ptr assembly = wfcWAssembly::cast(model);
if (assembly) {
CollectComponentsRecursively(assembly, "", components);
}
} else {
ShrinkwrapComponentInfo comp_info;
comp_info.id = StringToStdString(model->GetFileName());
comp_info.name = comp_info.id;
comp_info.type = "part";
comp_info.path = comp_info.id;
comp_info.volume = CalculateModelVolume(model);
comp_info.is_external = true;
components.push_back(comp_info);
}
} catch (...) {
// Analysis failed
}
return components;
}
void ShrinkwrapManager::CollectComponentsRecursively(wfcWAssembly_ptr assembly,
const std::string& parent_path,
std::vector<ShrinkwrapComponentInfo>& components) {
if (!assembly) return;
try {
pfcFeatures_ptr features = assembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT);
if (!features) return;
for (int i = 0; i < features->getarraysize(); i++) {
try {
pfcFeature_ptr feature = features->get(i);
pfcComponentFeat_ptr comp_feat = pfcComponentFeat::cast(feature);
if (comp_feat) {
std::string current_path = parent_path.empty() ? "" : parent_path + "/";
ShrinkwrapComponentInfo comp_info = CreateComponentInfo(comp_feat, current_path);
components.push_back(comp_info);
try {
auto model_descr = comp_feat->GetModelDescr();
if (model_descr && model_descr->GetType() == pfcMDL_ASSEMBLY) {
SessionInfo session_info = GetSessionInfo();
if (session_info.is_valid) {
pfcModel_ptr child_model = session_info.session->GetModelFromDescr(model_descr);
if (child_model) {
wfcWAssembly_ptr child_assembly = wfcWAssembly::cast(child_model);
if (child_assembly) {
CollectComponentsRecursively(child_assembly, comp_info.path, components);
}
}
}
}
} catch (...) {
continue;
}
}
} catch (...) {
continue;
}
}
} catch (...) {
// Feature listing failed
}
}
ShrinkwrapComponentInfo ShrinkwrapManager::CreateComponentInfo(pfcComponentFeat_ptr comp_feat, const std::string& path) {
ShrinkwrapComponentInfo comp_info;
try {
auto model_descr = comp_feat->GetModelDescr();
if (model_descr) {
xstring filename_xstr = model_descr->GetFileName();
if (filename_xstr != xstringnil && !filename_xstr.IsEmpty()) {
comp_info.id = StringToStdString(filename_xstr);
comp_info.name = comp_info.id;
size_t dot_pos = comp_info.name.find_last_of(".");
if (dot_pos != std::string::npos) {
comp_info.name = comp_info.name.substr(0, dot_pos);
}
}
if (model_descr->GetType() == pfcMDL_ASSEMBLY) {
comp_info.type = "assembly";
} else {
comp_info.type = "part";
}
}
comp_info.path = path + comp_info.id;
comp_info.file_size = "Unknown";
comp_info.volume = 0.0;
comp_info.is_external = false;
} catch (...) {
comp_info.id = "unknown_component";
comp_info.name = "Unknown Component";
comp_info.type = "part";
comp_info.path = path + comp_info.id;
}
return comp_info;
}
double ShrinkwrapManager::CalculateModelVolume(pfcModel_ptr model) {
if (!model) return 0.0;
@ -309,44 +252,37 @@ double ShrinkwrapManager::CalculateModelVolume(pfcModel_ptr model) {
pfcModel_ptr ShrinkwrapManager::ExecuteOTKShrinkwrap(pfcModel_ptr source_model, const ShrinkwrapShellRequest& request) {
if (!source_model) return nullptr;
SessionInfo session_info = GetSessionInfo();
if (!session_info.is_valid) return nullptr;
try {
pfcSolid_ptr source_solid = pfcSolid::cast(source_model);
if (!source_solid) return nullptr;
std::string output_path = request.output_file_path;
size_t slash_pos = output_path.find_last_of("/\\");
std::string filename = (slash_pos != std::string::npos) ?
output_path.substr(slash_pos + 1) : output_path;
size_t dot_pos = filename.find_last_of(".");
if (dot_pos != std::string::npos) {
filename = filename.substr(0, dot_pos);
}
// 生成唯一的Part名称避免重名冲突
std::string unique_filename = filename;
// Generate safe Part name based on source model
std::string base_name = GenerateSafePartName(source_model);
// Ensure unique name by checking existing models
std::string unique_filename = base_name;
int counter = 1;
xstring test_name = StringToXString(unique_filename);
// 检查名称是否已存在,如果存在则添加序号
// Check if name already exists, add counter if necessary
while (true) {
try {
pfcModel_ptr existing_model = session_info.session->GetModel(test_name, pfcMDL_PART);
if (existing_model) {
// 名称已存在,尝试下一个序号
unique_filename = filename + "_" + std::to_string(counter);
// Name exists, try next counter
unique_filename = base_name + "_" + std::to_string(counter);
test_name = StringToXString(unique_filename);
counter++;
} else {
// 名称不存在,可以使用
// Name doesn't exist, can use it
break;
}
} catch (...) {
// GetModel抛出异常表示模型不存在,可以使用这个名称
// GetModel throws exception if model doesn't exist, name is available
break;
}
}
@ -409,188 +345,49 @@ pfcModel_ptr ShrinkwrapManager::ExecuteOTKShrinkwrap(pfcModel_ptr source_model,
}
}
void ShrinkwrapManager::AnalyzeShrinkwrapDifferences(const std::vector<ShrinkwrapComponentInfo>& original_components,
pfcModel_ptr shrinkwrap_model,
ShrinkwrapShellResult& result) {
if (!shrinkwrap_model || original_components.empty()) {
return;
}
try {
double shrinkwrap_volume = CalculateModelVolume(shrinkwrap_model);
double original_total_volume = 0.0;
for (size_t i = 0; i < original_components.size(); i++) {
const ShrinkwrapComponentInfo& comp = original_components[i];
original_total_volume += comp.volume;
}
double preservation_ratio = (original_total_volume > 0) ?
(shrinkwrap_volume / original_total_volume) : 0.0;
for (size_t i = 0; i < original_components.size(); i++) {
const ShrinkwrapComponentInfo& comp = original_components[i];
FeatureDeletion deletion;
deletion.id = std::hash<std::string>{}(comp.id) % 10000;
deletion.name = comp.name;
deletion.type = comp.type;
deletion.part_file = comp.id;
deletion.part_path = comp.path;
deletion.component_type = "COMPONENT";
deletion.volume_reduction = static_cast<int>(comp.volume * 100 / (original_total_volume + 1));
bool likely_internal = (comp.volume < original_total_volume * 0.05) ||
(std::count(comp.path.begin(), comp.path.end(), '/') > 2);
if (likely_internal) {
deletion.reason = "Internal component removed during shrinkwrap";
deletion.confidence = 0.8;
result.safe_deletions.push_back(deletion);
} else {
deletion.reason = "External component preserved in shrinkwrap";
deletion.confidence = 0.9;
result.preserve_list.push_back(deletion);
}
}
} catch (...) {
for (size_t i = 0; i < original_components.size(); i++) {
const ShrinkwrapComponentInfo& comp = original_components[i];
FeatureDeletion deletion;
deletion.id = std::hash<std::string>{}(comp.id) % 10000;
deletion.name = comp.name;
deletion.type = comp.type;
deletion.reason = "Analysis unavailable";
deletion.confidence = 0.5;
deletion.volume_reduction = 0;
deletion.part_file = comp.id;
deletion.part_path = comp.path;
deletion.component_type = "COMPONENT";
result.preserve_list.push_back(deletion);
}
}
}
EstimatedReduction ShrinkwrapManager::CalculateReductionEstimates(const std::vector<ShrinkwrapComponentInfo>& original,
const std::vector<ShrinkwrapComponentInfo>& preserved) {
EstimatedReduction reduction;
try {
double original_volume = 0.0;
for (size_t i = 0; i < original.size(); i++) {
const ShrinkwrapComponentInfo& comp = original[i];
original_volume += comp.volume;
}
double preserved_volume = 0.0;
for (size_t i = 0; i < preserved.size(); i++) {
const ShrinkwrapComponentInfo& comp = preserved[i];
preserved_volume += comp.volume;
}
if (original_volume > 0) {
double volume_reduction_percent = ((original_volume - preserved_volume) / original_volume) * 100.0;
volume_reduction_percent = std::max(0.0, std::min(100.0, volume_reduction_percent));
std::stringstream ss;
ss << std::fixed << std::setprecision(1) << volume_reduction_percent << "%";
reduction.volume_reduction = ss.str();
} else {
reduction.volume_reduction = "0.0%";
}
double file_reduction_percent = std::max(0.0,
std::stod(reduction.volume_reduction.substr(0, reduction.volume_reduction.length()-1)) * 0.8);
std::stringstream ss2;
ss2 << std::fixed << std::setprecision(1) << file_reduction_percent << "%";
reduction.file_size_reduction = ss2.str();
double performance_improvement = std::max(100.0,
(static_cast<double>(original.size()) / std::max(1.0, static_cast<double>(preserved.size()))) * 100.0);
std::stringstream ss3;
ss3 << std::fixed << std::setprecision(0) << performance_improvement << "%";
reduction.performance_improvement = ss3.str();
} catch (...) {
reduction.volume_reduction = "75.0%";
reduction.file_size_reduction = "60.0%";
reduction.performance_improvement = "200%";
}
return reduction;
}
void ShrinkwrapManager::FormatShrinkwrapResults(const std::vector<ShrinkwrapComponentInfo>& original_components,
const std::vector<ShrinkwrapComponentInfo>& preserved_components,
const ShrinkwrapShellRequest& request,
ShrinkwrapShellResult& result) {
result.parameters.method = "creo_native_shrinkwrap";
result.parameters.quality = request.quality;
result.parameters.chord_height = request.chord_height;
result.parameters.fill_holes = request.fill_holes;
result.parameters.ignore_small_surfaces = request.ignore_small_surfaces;
result.parameters.small_surface_percentage = request.small_surface_percentage;
result.parameters.output_type = request.output_type;
result.parameters.ignore_quilts = request.ignore_quilts;
result.parameters.ignore_skeleton = request.ignore_skeleton;
result.parameters.assign_mass_properties = request.assign_mass_properties;
result.parameters.output_file_path = request.output_file_path;
result.parameters.preset_used = request.preset;
result.parameters.output_file_size = CalculateFileSize(request.output_file_path);
result.parameters.shrinkwrap_time = GetCurrentTimeString();
result.parameters.original_components_count = static_cast<int>(original_components.size());
result.parameters.preserved_components_count = static_cast<int>(preserved_components.size());
result.parameters.removed_components_count = result.parameters.original_components_count -
result.parameters.preserved_components_count;
result.estimated_reduction = CalculateReductionEstimates(original_components, preserved_components);
result.message = "Shrinkwrap shell export completed successfully. Output saved as .prt format regardless of input extension.";
}
ShrinkwrapShellResult ShrinkwrapManager::ExecuteShrinkwrapShell(const ShrinkwrapShellRequest& request) {
ShrinkwrapShellResult result;
// 创建请求副本以应用预设参数
// Apply preset parameters (if specified)
ShrinkwrapShellRequest processed_request = request;
// 应用预设参数(如果指定)
if (!processed_request.preset.empty()) {
ApplyPresetParameters(processed_request);
}
// Validate request
std::string validation_error;
if (!ValidateRequest(processed_request, validation_error)) {
result.success = false;
result.error_message = validation_error;
return result;
}
// Check Creo session
SessionInfo session_info = GetSessionInfo();
if (!session_info.is_valid) {
result.success = false;
result.error_message = "Creo session not available";
return result;
}
try {
// Get current model
pfcModel_ptr current_model = session_info.session->GetCurrentModel();
if (!current_model) {
result.success = false;
result.error_message = "No current model loaded";
return result;
}
// 检查当前模型类型Shrinkwrap外壳功能适用于装配体和零件
// Check model type - Shrinkwrap works for both assembly and part
if (current_model->GetType() != pfcMDL_ASSEMBLY && current_model->GetType() != pfcMDL_PART) {
result.success = false;
result.error_message = "Shrinkwrap shell export requires a part or assembly model. Current model type is not supported.";
return result;
}
// Execute Shrinkwrap directly on top-level model
pfcModel_ptr shrinkwrap_model = nullptr;
try {
shrinkwrap_model = ExecuteOTKShrinkwrap(current_model, processed_request);
@ -615,45 +412,45 @@ ShrinkwrapShellResult ShrinkwrapManager::ExecuteShrinkwrapShell(const Shrinkwrap
result.error_message = "Unknown Error: An unexpected error occurred during shrinkwrap processing.";
return result;
}
if (!shrinkwrap_model) {
result.success = false;
result.error_message = "Shrinkwrap Processing Failed: Unable to generate shrinkwrap model. Check if the current model is valid and not corrupted.";
return result;
}
// 获取实际保存的文件信息
// Get output file information
try {
SessionInfo session_info = GetSessionInfo();
if (session_info.is_valid) {
xstring current_dir = session_info.session->GetCurrentDirectory();
std::string actual_save_path = StringToStdString(current_dir);
xstring model_name = shrinkwrap_model->GetFileName();
std::string actual_filename = StringToStdString(model_name);
result.parameters.output_file_path = actual_save_path + "\\" + actual_filename;
result.parameters.output_file_size = CalculateFileSize(result.parameters.output_file_path);
result.parameters.shrinkwrap_time = GetCurrentTimeString();
// 设置基本参数信息
result.parameters.method = "creo_native_shrinkwrap";
result.parameters.quality = processed_request.quality;
result.parameters.fill_holes = processed_request.fill_holes;
result.parameters.ignore_small_surfaces = processed_request.ignore_small_surfaces;
result.parameters.small_surface_percentage = processed_request.small_surface_percentage;
result.parameters.ignore_quilts = processed_request.ignore_quilts;
result.parameters.ignore_skeleton = processed_request.ignore_skeleton;
result.parameters.assign_mass_properties = processed_request.assign_mass_properties;
}
} catch (...) {
// 获取路径失败,使用基本信息
xstring current_dir = session_info.session->GetCurrentDirectory();
std::string actual_save_path = StringToStdString(current_dir);
xstring model_name = shrinkwrap_model->GetFileName();
std::string actual_filename = StringToStdString(model_name);
// Set result parameters
result.parameters.output_file_path = actual_save_path + "\\" + actual_filename;
result.parameters.output_file_size = CalculateFileSize(result.parameters.output_file_path);
result.parameters.shrinkwrap_time = GetCurrentTimeString();
result.parameters.method = "creo_native_shrinkwrap";
result.parameters.quality = processed_request.quality;
result.parameters.fill_holes = processed_request.fill_holes;
result.parameters.ignore_small_surfaces = processed_request.ignore_small_surfaces;
result.parameters.small_surface_percentage = processed_request.small_surface_percentage;
result.parameters.ignore_quilts = processed_request.ignore_quilts;
result.parameters.ignore_skeleton = processed_request.ignore_skeleton;
result.parameters.assign_mass_properties = processed_request.assign_mass_properties;
result.parameters.preset_used = processed_request.preset;
result.success = true;
result.message = "Shrinkwrap shell export completed successfully";
} catch (...) {
// Failed to get file info, but shrinkwrap succeeded
result.parameters.method = "creo_native_shrinkwrap";
result.parameters.quality = processed_request.quality;
result.success = true;
result.message = "Shrinkwrap completed but file info unavailable";
}
result.success = true;
} catch (const std::exception& e) {
result.success = false;
result.error_message = "Standard error: " + std::string(e.what());
@ -661,6 +458,6 @@ ShrinkwrapShellResult ShrinkwrapManager::ExecuteShrinkwrapShell(const Shrinkwrap
result.success = false;
result.error_message = "Unknown error during shrinkwrap operation";
}
return result;
}

View File

@ -15,24 +15,7 @@
#include <map>
#include <memory>
// 复用CreoManager的数据结构定义
struct FeatureDeletion {
int id;
std::string name;
std::string type;
std::string reason;
double confidence;
int volume_reduction;
std::string part_file;
std::string part_path;
std::string component_type = "COMPONENT";
};
struct EstimatedReduction {
std::string volume_reduction;
std::string file_size_reduction;
std::string performance_improvement;
};
// Simplified data structures for Shrinkwrap
// Shrinkwrap特定的数据结构
struct ShrinkwrapShellRequest {
@ -67,28 +50,12 @@ struct ShrinkwrapShellParameters {
std::string output_file_path;
std::string output_file_size;
std::string shrinkwrap_time;
int original_components_count;
int preserved_components_count;
int removed_components_count;
std::string preset_used;
};
struct ShrinkwrapComponentInfo {
std::string id;
std::string name;
std::string type;
std::string path;
std::string file_size;
double volume;
bool is_external; // 是否接触外表面
};
struct ShrinkwrapShellResult {
bool success = false;
std::vector<FeatureDeletion> safe_deletions; // 被移除的内部组件
std::vector<FeatureDeletion> suggested_deletions; // 建议删除的组件
std::vector<FeatureDeletion> preserve_list; // 保留的外表面组件
EstimatedReduction estimated_reduction;
ShrinkwrapShellParameters parameters;
std::string message;
std::string error_message;
@ -114,34 +81,14 @@ private:
xstring StringToXString(const std::string& str);
std::string GetCurrentTimeString();
std::string CalculateFileSize(const std::string& file_path);
// 装配体分析方法
std::vector<ShrinkwrapComponentInfo> AnalyzeAssemblyComponents(pfcModel_ptr model);
void CollectComponentsRecursively(wfcWAssembly_ptr assembly,
const std::string& parent_path,
std::vector<ShrinkwrapComponentInfo>& components);
ShrinkwrapComponentInfo CreateComponentInfo(pfcComponentFeat_ptr comp_feat,
const std::string& path);
// 差异分析方法
void AnalyzeShrinkwrapDifferences(const std::vector<ShrinkwrapComponentInfo>& original_components,
pfcModel_ptr shrinkwrap_model,
ShrinkwrapShellResult& result);
std::string GenerateSafePartName(pfcModel_ptr source_model);
// OTK Shrinkwrap执行方法
pfcModel_ptr ExecuteOTKShrinkwrap(pfcModel_ptr source_model,
const ShrinkwrapShellRequest& request);
// 结果格式化方法
void FormatShrinkwrapResults(const std::vector<ShrinkwrapComponentInfo>& original_components,
const std::vector<ShrinkwrapComponentInfo>& preserved_components,
const ShrinkwrapShellRequest& request,
ShrinkwrapShellResult& result);
// 质量属性计算
double CalculateModelVolume(pfcModel_ptr model);
EstimatedReduction CalculateReductionEstimates(const std::vector<ShrinkwrapComponentInfo>& original,
const std::vector<ShrinkwrapComponentInfo>& preserved);
public:
// 单例模式