This commit is contained in:
2026-05-11 14:15:39 +08:00
parent a68b9ca57f
commit eaf37b9a03
2 changed files with 1401 additions and 0 deletions
+874
View File
@@ -0,0 +1,874 @@
# models/database.py
from sqlalchemy import Column, Integer, String, Text, DateTime, Date, JSON, LargeBinary, Boolean, Float, ForeignKey, UniqueConstraint, Numeric
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from datetime import datetime, date
Base = declarative_base()
class User(Base):
"""用户表"""
__tablename__ = "users"
__excluded_fields__ = {'hashed_password'}
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, index=True, nullable=False)
email = Column(String(255), unique=True, index=True, nullable=False)
hashed_password = Column(String(255), nullable=False)
full_name = Column(String(100))
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=func.now())
last_login = Column(DateTime, nullable=True)
stp_files = relationship("STPFile", back_populates="user")
user_roles = relationship("UserRole", back_populates="user", cascade="all, delete-orphan")
@property
def roles(self):
return [ur.role for ur in self.user_roles]
@property
def is_superuser(self):
return any(r.code == 'admin' for r in self.roles)
def has_permission(self, permission_code: str) -> bool:
if self.is_superuser:
return True
for role in self.roles:
for perm in role.permissions:
if perm.code == permission_code:
return True
return False
def safe_dict(self):
return {k: v for k, v in self.__dict__.items()
if not k.startswith('_') and k not in self.__excluded_fields__}
def __repr__(self):
return f"<User(id={self.id}, username='{self.username}')>"
class Role(Base):
"""角色表"""
__tablename__ = "roles"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(50), unique=True, index=True, nullable=False)
name = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
is_system = Column(Boolean, default=False)
created_at = Column(DateTime, default=func.now())
user_roles = relationship("UserRole", back_populates="role", cascade="all, delete-orphan")
role_permissions = relationship("RolePermission", back_populates="role", cascade="all, delete-orphan")
@property
def permissions(self):
return [rp.permission for rp in self.role_permissions]
def __repr__(self):
return f"<Role(code='{self.code}', name='{self.name}')>"
class Permission(Base):
"""权限表"""
__tablename__ = "permissions"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(100), unique=True, index=True, nullable=False)
name = Column(String(100), nullable=False)
module = Column(String(50), nullable=True)
description = Column(Text, nullable=True)
created_at = Column(DateTime, default=func.now())
role_permissions = relationship("RolePermission", back_populates="permission", cascade="all, delete-orphan")
def __repr__(self):
return f"<Permission(code='{self.code}', name='{self.name}')>"
class UserRole(Base):
"""用户角色关联表"""
__tablename__ = "user_roles"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False, index=True)
created_at = Column(DateTime, default=func.now())
user = relationship("User", back_populates="user_roles")
role = relationship("Role", back_populates="user_roles")
def __repr__(self):
return f"<UserRole(user_id={self.user_id}, role_id={self.role_id})>"
class RolePermission(Base):
"""角色权限关联表"""
__tablename__ = "role_permissions"
id = Column(Integer, primary_key=True, index=True)
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False, index=True)
permission_id = Column(Integer, ForeignKey("permissions.id"), nullable=False, index=True)
created_at = Column(DateTime, default=func.now())
role = relationship("Role", back_populates="role_permissions")
permission = relationship("Permission", back_populates="role_permissions")
def __repr__(self):
return f"<RolePermission(role_id={self.role_id}, permission_id={self.permission_id})>"
class STPFile(Base):
"""STP源文件元数据表 - 支持同一文件多次上传"""
__tablename__ = "stp_files"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
# 对象存储信息
object_key = Column(String(500), nullable=False, index=True) # MinIO对象键
storage_bucket = Column(String(100), nullable=False) # 存储桶名称
object_url = Column(String(1000), nullable=True) # 预签名URL(可选)
# 文件信息
original_filename = Column(String(255), nullable=False, index=True) # 添加索引支持按文件名查询
file_size = Column(Integer, nullable=False)
file_hash = Column(String(64), index=True) # 移除unique约束,允许同一文件多次上传
mime_type = Column(String(50), default="application/octet-stream")
# 上传批次标识 - 用于区分同一文件的多次上传
upload_batch = Column(String(36), index=True) # UUID批次号
# 时间戳
upload_time = Column(DateTime, default=func.now())
processed_time = Column(DateTime, nullable=True)
# 状态
status = Column(String(20), default="pending", index=True) # pending, processing, completed, failed
error_message = Column(Text, nullable=True)
# 分析摘要 - 快速查询字段
volume = Column(Float, nullable=True) # 体积 mm³
surface_area = Column(Float, nullable=True) # 表面积 mm²
product_weight = Column(Float, nullable=True) # 产品重量 g
# 保留旧字段以兼容
file_path = Column(String(500), nullable=True) # 本地路径(已弃用)
file_content = Column(LargeBinary, nullable=True) # 本地存储(已弃用)
filename = Column(String(255), nullable=True) # 已弃用
# 关联关系
user = relationship("User", back_populates="stp_files")
geometry_data = relationship("GeometryData", back_populates="stp_file", uselist=False)
mesh_data = relationship("MeshData", back_populates="stp_file", uselist=False)
mold_cavity_data = relationship("MoldCavityData", back_populates="stp_file", uselist=False)
html_file = relationship("HTMLFile", back_populates="stp_file", uselist=False)
analysis_metrics = relationship("AnalysisMetrics", back_populates="stp_file", uselist=False)
feature_detections = relationship("FeatureDetection", back_populates="stp_file")
design_recommendations = relationship("DesignRecommendation", back_populates="stp_file")
processing_tasks = relationship("ProcessingTask", back_populates="stp_file")
def __repr__(self):
return f"<STPFile(id={self.id}, original_filename='{self.original_filename}', status='{self.status}')>"
class GeometryData(Base):
"""几何数据JSON元数据表"""
__tablename__ = "geometry_data"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 对象存储信息
object_key = Column(String(500), nullable=False)
storage_bucket = Column(String(100), nullable=False)
object_url = Column(String(1000), nullable=True)
# 分析方法
analysis_method = Column(String(50), default="pythonocc") # pythonocc, simulated
# 时间戳
created_time = Column(DateTime, default=func.now())
# 几何属性摘要(便于快速查询)
volume = Column(Float, nullable=True)
surface_area = Column(Float, nullable=True)
bounding_box_min = Column(JSON, nullable=True)
bounding_box_max = Column(JSON, nullable=True)
center_of_mass = Column(JSON, nullable=True)
# 拓扑信息
topology_faces = Column(Integer, nullable=True)
topology_edges = Column(Integer, nullable=True)
topology_vertices = Column(Integer, nullable=True)
# 关联关系
stp_file = relationship("STPFile", back_populates="geometry_data")
def __repr__(self):
return f"<GeometryData(id={self.id}, stp_file_id={self.stp_file_id})>"
class MeshData(Base):
"""网格数据JSON元数据表(详细网格存 RustFS,PostgreSQL 存摘要)"""
__tablename__ = "mesh_data"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 对象存储信息
object_key = Column(String(500), nullable=False)
storage_bucket = Column(String(100), nullable=False)
object_url = Column(String(1000), nullable=True)
# 生成设置
quality = Column(String(20), default="medium") # low / medium / high
# 网格规模信息
vertex_count = Column(Integer, nullable=True)
face_count = Column(Integer, nullable=True)
point_count = Column(Integer, nullable=True) # 采样点云数量
# 网格边界框(便于快速查询)
bounding_box_min = Column(JSON, nullable=True)
bounding_box_max = Column(JSON, nullable=True)
# 时间戳
created_time = Column(DateTime, default=func.now())
# 关联关系
stp_file = relationship("STPFile", back_populates="mesh_data")
def __repr__(self):
return f"<MeshData(id={self.id}, stp_file_id={self.stp_file_id}, quality='{self.quality}')>"
class HTMLFile(Base):
"""网页文件元数据表"""
__tablename__ = "html_files"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 对象存储信息
object_key = Column(String(500), nullable=False)
storage_bucket = Column(String(100), nullable=False)
object_url = Column(String(1000), nullable=True)
# 文件信息
filename = Column(String(255), nullable=False)
generated_time = Column(DateTime, default=func.now())
# 可视化相关元数据
visualization_type = Column(String(50), default="3d_viewer")
has_interactive_elements = Column(Boolean, default=True)
# 保留旧字段以兼容
file_path = Column(String(500), nullable=True)
html_content = Column(Text, nullable=True)
# 关联关系
stp_file = relationship("STPFile", back_populates="html_file")
def __repr__(self):
return f"<HTMLFile(id={self.id}, stp_file_id={self.stp_file_id}, object_key='{self.object_key}')>"
class ProcessingTask(Base):
"""处理任务记录表"""
__tablename__ = "processing_tasks"
id = Column(Integer, primary_key=True, index=True)
task_id = Column(String(36), unique=True, index=True, nullable=False)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 任务类型和状态
task_type = Column(String(50), default="stp_parsing") # stp_parsing, geometry_analysis, mold_generation
status = Column(String(20), default="pending") # pending, processing, completed, failed
# 时间戳
created_time = Column(DateTime, default=func.now())
started_time = Column(DateTime, nullable=True)
completed_time = Column(DateTime, nullable=True)
# 处理进度
progress = Column(Integer, default=0) # 0-100
current_step = Column(String(100), nullable=True)
# 错误信息
error_message = Column(Text, nullable=True)
error_stack = Column(Text, nullable=True)
# 处理参数
parameters = Column(JSON, nullable=True) # 任务参数
# 关联关系
stp_file = relationship("STPFile", back_populates="processing_tasks")
def __repr__(self):
return f"<ProcessingTask(id={self.id}, task_id='{self.task_id}', status='{self.status}')>"
class MoldCavityData(Base):
"""模具型腔数据元数据表"""
__tablename__ = "mold_cavity_data"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 对象存储信息
detailed_object_key = Column(String(500), nullable=False) # 完整三维数据
storage_bucket = Column(String(100), nullable=False)
# 模具类型和材料
mold_material = Column(String(100), default="Aluminum Alloy 7075")
mold_type = Column(String(50), default="single_cavity") # single_cavity, multi_cavity
# 工艺参数
shrinkage_rate = Column(Float, nullable=False)
draft_angle = Column(Float, nullable=False)
parting_line_length = Column(Float, nullable=True)
# 生成时间
generated_time = Column(DateTime, default=func.now())
# 关键信息摘要(快速查询字段)
cavity_key_info = Column(JSON, nullable=True) # 完整关键信息
# 提取的字段(便于查询和排序)
mold_size_length = Column(Float, nullable=True)
mold_size_width = Column(Float, nullable=True)
mold_size_height = Column(Float, nullable=True)
estimated_clamping_force = Column(String(50), nullable=True)
product_weight = Column(String(50), nullable=True)
product_volume = Column(Float, nullable=True)
wall_thickness_range = Column(String(50), nullable=True)
complexity_score = Column(Float, nullable=True)
# 质量评估
weld_line_risk = Column(String(50), nullable=True) # 熔接痕风险
sink_mark_risk = Column(String(50), nullable=True) # 缩痕风险
warpage_risk = Column(String(50), nullable=True) # 翘曲风险
# 多方案可信化摘要(第1周阶段1)
best_scheme_id = Column(String(64), nullable=True, index=True)
confidence_score = Column(Float, nullable=True)
is_fallback = Column(Boolean, nullable=True, index=True)
fallback_reason = Column(Text, nullable=True)
# 关联关系
stp_file = relationship("STPFile", back_populates="mold_cavity_data")
def __repr__(self):
return f"<MoldCavityData(stp_file_id={self.stp_file_id}, mold_material='{self.mold_material}')>"
class FeatureDetection(Base):
"""特征检测结果表"""
__tablename__ = "feature_detections"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 特征信息
feature_type = Column(String(50), nullable=False, index=True) # thin_wall, thick_wall, wall_non_uniform, rib, boss, draft_angle, high_curvature, fillet
confidence = Column(Float, nullable=False) # 0.0 - 1.0
# 位置和尺寸
location = Column(JSON, nullable=True) # [x, y, z]
dimensions = Column(JSON, nullable=True) # [length, width, height]
# 特征参数
parameters = Column(JSON, nullable=True) # 自定义参数
# 检测时间
detected_at = Column(DateTime, default=func.now())
# 关联的几何数据
geometry_data_id = Column(Integer, ForeignKey("geometry_data.id"), nullable=True)
# 关联关系
stp_file = relationship("STPFile", back_populates="feature_detections")
def __repr__(self):
return f"<FeatureDetection(id={self.id}, feature_type='{self.feature_type}', confidence={self.confidence})>"
class DesignRecommendation(Base):
"""设计建议表"""
__tablename__ = "design_recommendations"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 建议信息
rec_type = Column(String(50), nullable=False) # wall_thickness, draft_angle, etc.
priority = Column(String(20), nullable=False) # high, medium, low
description = Column(String(500), nullable=False)
reason = Column(Text, nullable=True)
# 建议参数
parameters = Column(JSON, nullable=True)
# 状态
status = Column(String(20), default="pending") # pending, accepted, rejected
user_notes = Column(Text, nullable=True)
# 时间戳
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, nullable=True)
# 关联关系
stp_file = relationship("STPFile", back_populates="design_recommendations")
def __repr__(self):
return f"<DesignRecommendation(id={self.id}, rec_type='{self.rec_type}', priority='{self.priority}')>"
class UserActivity(Base):
"""用户活动日志表"""
__tablename__ = "user_activities"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
# 活动信息
activity_type = Column(String(50), nullable=False, index=True) # upload, view, download, delete, export
resource_type = Column(String(50), nullable=True) # stp_file, geometry_data, mold_cavity
resource_id = Column(Integer, nullable=True)
# 活动详情
description = Column(Text, nullable=True)
meta_data = Column(JSON, nullable=True)
# 时间戳
created_at = Column(DateTime, default=func.now(), index=True)
# IP和设备信息
ip_address = Column(String(45), nullable=True)
user_agent = Column(String(500), nullable=True)
def __repr__(self):
return f"<UserActivity(id={self.id}, user_id={self.user_id}, activity_type='{self.activity_type}')>"
class SystemLog(Base):
"""系统日志表(重要操作和错误)"""
__tablename__ = "system_logs"
id = Column(Integer, primary_key=True, index=True)
# 日志级别
level = Column(String(20), nullable=False, index=True) # INFO, WARNING, ERROR, CRITICAL
# 日志信息
message = Column(Text, nullable=False)
module = Column(String(100), nullable=True) # 模块名
function_name = Column(String(100), nullable=True)
# 时间戳
created_at = Column(DateTime, default=func.now(), index=True)
# 用户信息(如果有关联用户)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# 额外信息
request_id = Column(String(100), nullable=True) # 关联的请求ID
execution_time_ms = Column(Integer, nullable=True) # 执行时间
# 关联数据
resource_type = Column(String(50), nullable=True)
resource_id = Column(Integer, nullable=True)
def __repr__(self):
return f"<SystemLog(id={self.id}, level='{self.level}', module='{self.module}')>"
class Product(Base):
"""产品表"""
__tablename__ = "products"
id = Column(Integer, primary_key=True, index=True)
sku = Column(String(50), unique=True, index=True, nullable=False)
name = Column(String(200), nullable=False)
description = Column(Text, nullable=True)
category = Column(String(100), nullable=True)
unit = Column(String(20), default="件")
item_type = Column(String(20), default="finished", index=True)
cost_price = Column(Numeric(12, 2), default=0)
sale_price = Column(Numeric(12, 2), default=0)
min_stock = Column(Integer, default=0)
max_stock = Column(Integer, default=1000)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
inventory = relationship("Inventory", back_populates="product", uselist=False)
stock_movements = relationship("StockMovement", back_populates="product")
bom_materials = relationship(
"ProductMaterial",
foreign_keys="ProductMaterial.finished_product_id",
back_populates="finished_product",
cascade="all, delete-orphan"
)
used_in_products = relationship(
"ProductMaterial",
foreign_keys="ProductMaterial.material_product_id",
back_populates="material_product"
)
def __repr__(self):
return f"<Product(id={self.id}, sku='{self.sku}', name='{self.name}')>"
class ProductMaterial(Base):
__tablename__ = "product_materials"
__table_args__ = (
UniqueConstraint("finished_product_id", "material_product_id", name="uq_product_material_unique"),
)
id = Column(Integer, primary_key=True, index=True)
finished_product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
material_product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
quantity = Column(Numeric(12, 4), nullable=False)
loss_rate = Column(Numeric(5, 4), default=0)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
finished_product = relationship(
"Product",
foreign_keys=[finished_product_id],
back_populates="bom_materials"
)
material_product = relationship(
"Product",
foreign_keys=[material_product_id],
back_populates="used_in_products"
)
def __repr__(self):
return f"<ProductMaterial(finished_product_id={self.finished_product_id}, material_product_id={self.material_product_id})>"
class MaterialPriceHistory(Base):
"""物料价格历史表"""
__tablename__ = "material_price_history"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
price = Column(Numeric(12, 2), nullable=False)
effective_date = Column(DateTime, default=func.now(), index=True)
supplier_id = Column(Integer, ForeignKey("suppliers.id"), nullable=True, index=True)
remark = Column(Text, nullable=True)
created_at = Column(DateTime, default=func.now())
product = relationship("Product", backref="price_history")
supplier = relationship("Supplier", backref="price_history")
def __repr__(self):
return f"<MaterialPriceHistory(product_id={self.product_id}, price={self.price}, date={self.effective_date})>"
class MaterialSupplier(Base):
"""物料供应商关联表"""
__tablename__ = "material_suppliers"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
supplier_id = Column(Integer, ForeignKey("suppliers.id"), nullable=False, index=True)
is_primary = Column(Boolean, default=False)
contact_person = Column(String(100), nullable=True)
contact_phone = Column(String(50), nullable=True)
lead_time = Column(Integer, nullable=True) # 交货周期(天)
min_order_quantity = Column(Integer, nullable=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
product = relationship("Product", backref="suppliers")
supplier = relationship("Supplier", backref="materials")
def __repr__(self):
return f"<MaterialSupplier(product_id={self.product_id}, supplier_id={self.supplier_id}, primary={self.is_primary})>"
class Supplier(Base):
"""供应商表"""
__tablename__ = "suppliers"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(50), unique=True, index=True)
name = Column(String(200), nullable=False)
contact_person = Column(String(100), nullable=True)
phone = Column(String(50), nullable=True)
email = Column(String(100), nullable=True)
address = Column(Text, nullable=True)
bank_name = Column(String(100), nullable=True)
bank_account = Column(String(50), nullable=True)
tax_number = Column(String(50), nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
purchase_orders = relationship("PurchaseOrder", back_populates="supplier")
def __repr__(self):
return f"<Supplier(id={self.id}, name='{self.name}')>"
class Customer(Base):
"""客户表"""
__tablename__ = "customers"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(50), unique=True, index=True)
name = Column(String(200), nullable=False)
contact_person = Column(String(100), nullable=True)
phone = Column(String(50), nullable=True)
email = Column(String(100), nullable=True)
address = Column(Text, nullable=True)
bank_name = Column(String(100), nullable=True)
bank_account = Column(String(50), nullable=True)
tax_number = Column(String(50), nullable=True)
credit_limit = Column(Numeric(12, 2), default=0)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
sales_orders = relationship("SalesOrder", back_populates="customer")
def __repr__(self):
return f"<Customer(id={self.id}, name='{self.name}')>"
class Warehouse(Base):
"""仓库表"""
__tablename__ = "warehouses"
id = Column(Integer, primary_key=True, index=True)
code = Column(String(50), unique=True, index=True)
name = Column(String(200), nullable=False)
address = Column(Text, nullable=True)
manager = Column(String(100), nullable=True)
phone = Column(String(50), nullable=True)
is_active = Column(Boolean, default=True)
is_default = Column(Boolean, default=False)
created_at = Column(DateTime, default=func.now())
inventories = relationship("Inventory", back_populates="warehouse")
def __repr__(self):
return f"<Warehouse(id={self.id}, name='{self.name}')>"
class Inventory(Base):
"""库存表"""
__tablename__ = "inventory"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
warehouse_id = Column(Integer, ForeignKey("warehouses.id"), nullable=False, index=True)
quantity = Column(Numeric(12, 4), default=0)
locked_quantity = Column(Numeric(12, 4), default=0)
batch_number = Column(String(50), nullable=True)
location = Column(String(100), nullable=True)
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
product = relationship("Product", back_populates="inventory")
warehouse = relationship("Warehouse", back_populates="inventories")
def __repr__(self):
return f"<Inventory(product_id={self.product_id}, quantity={self.quantity})>"
@property
def available_quantity(self):
return self.quantity - self.locked_quantity
class StockMovement(Base):
"""库存变动记录表"""
__tablename__ = "stock_movements"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False, index=True)
warehouse_id = Column(Integer, ForeignKey("warehouses.id"), nullable=False)
movement_type = Column(String(20), nullable=False)
quantity = Column(Numeric(12, 4), nullable=False)
before_quantity = Column(Numeric(12, 4), default=0)
after_quantity = Column(Numeric(12, 4), default=0)
reference_type = Column(String(50), nullable=True)
reference_id = Column(Integer, nullable=True)
reference_no = Column(String(50), nullable=True)
unit_price = Column(Numeric(12, 2), nullable=True)
total_amount = Column(Numeric(12, 2), nullable=True)
remark = Column(Text, nullable=True)
operator_id = Column(Integer, ForeignKey("users.id"), nullable=True)
created_at = Column(DateTime, default=func.now(), index=True)
product = relationship("Product", back_populates="stock_movements")
def __repr__(self):
return f"<StockMovement(id={self.id}, type='{self.movement_type}', qty={self.quantity})>"
class PurchaseOrder(Base):
"""采购订单表"""
__tablename__ = "purchase_orders"
id = Column(Integer, primary_key=True, index=True)
order_no = Column(String(50), unique=True, index=True, nullable=False)
supplier_id = Column(Integer, ForeignKey("suppliers.id"), nullable=False, index=True)
order_date = Column(DateTime, default=func.now())
expected_date = Column(Date, nullable=True)
status = Column(String(20), default="draft")
total_amount = Column(Numeric(12, 2), default=0)
paid_amount = Column(Numeric(12, 2), default=0)
remark = Column(Text, nullable=True)
operator_id = Column(Integer, ForeignKey("users.id"), nullable=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
# 状态变更时间
received_date = Column(DateTime, nullable=True) # 已收货时间
paid_date = Column(DateTime, nullable=True) # 已付款时间
supplier = relationship("Supplier", back_populates="purchase_orders")
items = relationship("PurchaseOrderItem", back_populates="order", cascade="all, delete-orphan")
def __repr__(self):
return f"<PurchaseOrder(order_no='{self.order_no}', status='{self.status}')>"
class PurchaseOrderItem(Base):
"""采购订单明细表"""
__tablename__ = "purchase_order_items"
id = Column(Integer, primary_key=True, index=True)
order_id = Column(Integer, ForeignKey("purchase_orders.id"), nullable=False)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False)
quantity = Column(Integer, nullable=False)
received_quantity = Column(Integer, default=0)
unit_price = Column(Numeric(12, 2), nullable=False)
amount = Column(Numeric(12, 2), nullable=False)
remark = Column(Text, nullable=True)
order = relationship("PurchaseOrder", back_populates="items")
def __repr__(self):
return f"<PurchaseOrderItem(order_id={self.order_id}, product_id={self.product_id})>"
class SalesOrder(Base):
"""销售订单表"""
__tablename__ = "sales_orders"
id = Column(Integer, primary_key=True, index=True)
order_no = Column(String(50), unique=True, index=True, nullable=False)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False, index=True)
order_date = Column(DateTime, default=func.now())
delivery_date = Column(Date, nullable=True)
manufacturing_date = Column(DateTime, nullable=True)
actual_delivery_date = Column(DateTime, nullable=True)
actual_payment_date = Column(DateTime, nullable=True)
status = Column(String(20), default="draft")
production_status = Column(String(20), default="not_started", index=True)
production_no = Column(String(50), nullable=True, index=True)
planned_material_cost = Column(Numeric(12, 2), default=0)
actual_material_cost = Column(Numeric(12, 2), default=0)
total_amount = Column(Numeric(12, 2), default=0)
received_amount = Column(Numeric(12, 2), default=0)
remark = Column(Text, nullable=True)
operator_id = Column(Integer, ForeignKey("users.id"), nullable=True)
created_at = Column(DateTime, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
customer = relationship("Customer", back_populates="sales_orders")
items = relationship("SalesOrderItem", back_populates="order", cascade="all, delete-orphan")
def __repr__(self):
return f"<SalesOrder(order_no='{self.order_no}', status='{self.status}')>"
class FinanceTransaction(Base):
__tablename__ = "finance_transactions"
id = Column(Integer, primary_key=True, index=True)
txn_no = Column(String(50), unique=True, index=True, nullable=False)
txn_type = Column(String(20), nullable=False, index=True)
partner_type = Column(String(20), nullable=False, index=True)
partner_id = Column(Integer, nullable=False, index=True)
amount = Column(Numeric(12, 2), nullable=False)
txn_date = Column(DateTime, default=func.now(), index=True)
method = Column(String(30), default="bank")
account_name = Column(String(100), nullable=True)
status = Column(String(20), default="confirmed", index=True)
remark = Column(Text, nullable=True)
operator_id = Column(Integer, ForeignKey("users.id"), nullable=True)
created_at = Column(DateTime, default=func.now(), index=True)
allocations = relationship("FinanceAllocation", back_populates="transaction", cascade="all, delete-orphan")
def __repr__(self):
return f"<FinanceTransaction(txn_no='{self.txn_no}', txn_type='{self.txn_type}', amount={self.amount})>"
class FinanceAllocation(Base):
__tablename__ = "finance_allocations"
id = Column(Integer, primary_key=True, index=True)
transaction_id = Column(Integer, ForeignKey("finance_transactions.id"), nullable=False, index=True)
order_type = Column(String(20), nullable=False, index=True)
order_id = Column(Integer, nullable=False, index=True)
allocated_amount = Column(Numeric(12, 2), nullable=False)
created_at = Column(DateTime, default=func.now(), index=True)
transaction = relationship("FinanceTransaction", back_populates="allocations")
def __repr__(self):
return f"<FinanceAllocation(transaction_id={self.transaction_id}, order_type='{self.order_type}', amount={self.allocated_amount})>"
class AnalysisMetrics(Base):
"""分析指标表"""
__tablename__ = "analysis_metrics"
id = Column(Integer, primary_key=True, index=True)
stp_file_id = Column(Integer, ForeignKey("stp_files.id"), nullable=False, index=True)
# 质量指标
volume_utilization = Column(Float, default=0) # 体积利用率
topology_complexity = Column(Float, default=0) # 拓扑复杂度
wall_uniformity = Column(Float, default=0) # 壁厚均匀性
# 分析摘要
analysis_summary = Column(Text, nullable=True)
# FreeCAD 验证结果
verification_status = Column(String(20), nullable=True) # passed, failed, pending, error
verification_volume_diff = Column(Float, nullable=True) # 体积差异百分比
verification_area_diff = Column(Float, nullable=True) # 表面积差异百分比
verification_details = Column(JSON, nullable=True) # 完整验证结果
# 时间戳
created_at = Column(DateTime, default=func.now())
# 关联关系
stp_file = relationship("STPFile", back_populates="analysis_metrics")
def __repr__(self):
return f"<AnalysisMetrics(stp_file_id={self.stp_file_id}, volume_utilization={self.volume_utilization})>"
class SalesOrderItem(Base):
"""销售订单明细表"""
__tablename__ = "sales_order_items"
id = Column(Integer, primary_key=True, index=True)
order_id = Column(Integer, ForeignKey("sales_orders.id"), nullable=False)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False)
quantity = Column(Integer, nullable=False)
delivered_quantity = Column(Integer, default=0)
unit_price = Column(Numeric(12, 2), nullable=False)
amount = Column(Numeric(12, 2), nullable=False)
remark = Column(Text, nullable=True)
order = relationship("SalesOrder", back_populates="items")
def __repr__(self):
return f"<SalesOrderItem(order_id={self.order_id}, product_id={self.product_id})>"
+527
View File
@@ -0,0 +1,527 @@
# services/processing_service.py
"""STP 文件处理流程编排器 — 协调解析、网格生成、型腔生成、保存、验证"""
import asyncio
import traceback
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from core.stp_parser import STPParser
from core.geometry_analyzer import GeometryAnalyzer
from core.mold_generator import MoldCavityGenerator
from core.aluminum_foam_mold import AluminumFoamMoldGenerator
from core.mold_quality_inspector import AluminumFoamMoldQualityInspector
from core.mesh_generator import MeshGenerator
from core.multi_scheme_planner import MultiSchemeMoldPlanner
from services.storage_integration_rustfs import StorageIntegrationService
from services.redis_task_manager import redis_task_manager
from services.material_service import MaterialService
from services.calculation_service import CalculationService
from services.llm_service import llm_service
from models.schemas import ProcessingStatus
from database.database import db_manager
from utils.html_generator import HTMLGenerator
from utils.logger import get_logger
logger = get_logger(__name__)
class ProcessingService:
"""核心处理流程编排 — 协调 STP 解析、网格、型腔、计算、保存、验证"""
def __init__(self):
self.stp_parser = STPParser()
self.geometry_analyzer = GeometryAnalyzer()
self.mold_generator = MoldCavityGenerator(shrinkage_rate=0.005)
self.aluminum_foam_generator = AluminumFoamMoldGenerator(shrinkage_rate=0.015, draft_angle=3.0)
self.mold_quality_inspector = AluminumFoamMoldQualityInspector()
self.mesh_generator = MeshGenerator(quality="medium")
self.html_generator = HTMLGenerator()
self.storage_service = StorageIntegrationService()
self.multi_scheme_planner = MultiSchemeMoldPlanner()
# ─── 对外入口 ───
async def process_file_with_storage(
self,
task_id: str,
file_path: str,
stp_file_id: int,
material: str = "ABS",
):
"""处理文件的后台任务 — 使用独立数据库会话"""
# 创建独立的数据库会话,避免请求范围会话关闭
async with db_manager.session() as db_session:
try:
logger.info(f"开始处理文件并生成模具型腔: {file_path}")
# 设置处理超时(5分钟)
timeout_seconds = 300
try:
await asyncio.wait_for(
self.process_file_core(
task_id, file_path, stp_file_id, db_session, material
),
timeout_seconds,
)
except asyncio.TimeoutError:
logger.error(f"处理超时: {task_id}")
raise Exception(f"处理超时,超过{timeout_seconds}秒未完成")
except Exception as e:
logger.error(f"模具型腔生成失败: {e}")
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "failed")
await self.storage_service.update_task_status(
db_session, task_id, "failed", error_message=str(e)
)
# 安全更新 Redis 任务状态
task = await redis_task_manager.get_task(task_id)
if task:
await redis_task_manager.update_task(task_id, {
"status": ProcessingStatus.FAILED,
"error": str(e),
"completed_at": str(datetime.now()),
})
async def process_file_core(
self,
task_id: str,
file_path: str,
stp_file_id: int,
db_session: AsyncSession,
material: str = "ABS",
):
"""核心处理逻辑"""
try:
logger.info(f"开始处理文件并生成模具型腔: {file_path}")
# 1. 解析STP文件
await self.storage_service.update_task_status(
db_session, task_id, "processing", 20, "解析STP文件"
)
shape = self.stp_parser.load_step_file(Path(file_path))
geometry_data = self.stp_parser.analyze_geometry(shape)
# 2. 生成网格数据并持久化
await self.storage_service.update_task_status(
db_session, task_id, "processing", 30, "生成网格数据"
)
mesh_result = await self._step_generate_mesh(
shape, geometry_data, file_path, db_session, stp_file_id, task_id
)
# 3. 生成模具型腔
await self.storage_service.update_task_status(
db_session, task_id, "processing", 40, "生成模具型腔"
)
# 材料属性 — 通过 MaterialService 集中管理
requested_material = MaterialService.resolve_material(material)
selected_material = MaterialService.get_material(requested_material)
is_foam_material = MaterialService.is_foam_material(requested_material)
plan_result = await self._step_generate_cavity(
shape, selected_material, is_foam_material
)
# 4. 生成详细JSON数据 — 委托 CalculationService
await self.storage_service.update_task_status(
db_session, task_id, "processing", 60, "生成型腔详细数据"
)
detailed_cavity_json = CalculationService.build_plan_result(
geometry_data=geometry_data,
material=selected_material,
file_path=str(file_path),
plan_result=plan_result,
)
best_scheme = CalculationService.get_best_scheme(detailed_cavity_json)
best_cavity_data = best_scheme.get("cavity_data", {}) if best_scheme else {}
best_key_info = best_scheme.get("key_info", {}) if best_scheme else {}
if best_cavity_data.get("mold_cavities"):
cavity_geometry = best_cavity_data["mold_cavities"].get("cavity", {})
logger.info(
f"推荐方案型腔数据已合并: cavity {cavity_geometry.get('vertex_count', 0)} 顶点"
)
# 5. 生成关键信息
cavity_key_info = best_key_info
# 6. 保存几何数据到数据库
await self.storage_service.update_task_status(
db_session, task_id, "processing", 70, "保存几何数据"
)
await self.storage_service.save_geometry_data(
db_session,
stp_file_id,
geometry_data,
geometry_data.get("analysis_method", "mold_cavity"),
)
# 7. 生成HTML可视化
await self.storage_service.update_task_status(
db_session, task_id, "processing", 85, "生成可视化报告"
)
pointcloud_data = None
lod_data = None
if mesh_result:
pointcloud_data = {
"points": mesh_result.get("points", []),
"normals": mesh_result.get("normals", []),
"vertices": mesh_result.get("vertices", []),
"faces": mesh_result.get("faces", []),
"point_count": mesh_result.get("point_count", 0),
"vertex_count": mesh_result.get("vertex_count", 0),
"face_count": mesh_result.get("face_count", 0),
}
# 生成多级LOD数据(用于前端按距离切换精度)
try:
lod_result = self.mesh_generator.generate_multi_lod_mesh(shape)
if lod_result and lod_result.get("lods"):
lod_data = lod_result
logger.info(f"LOD数据生成成功: {len(lod_result['lods'])} 级 (面数: {[lod_result['lods'][k]['face_count'] for k in sorted(lod_result['lods'].keys())]})")
except Exception as lod_err:
logger.warning(f"LOD数据生成失败,使用单级精度: {lod_err}")
detailed_cavity_json = await self._attach_scheme_previews(
detailed_cavity_json=detailed_cavity_json,
geometry_data=geometry_data,
stp_filename=Path(file_path).name,
pointcloud_data=pointcloud_data,
lod_data=lod_data,
)
best_scheme = CalculationService.get_best_scheme(detailed_cavity_json)
best_cavity_data = best_scheme.get("cavity_data", {}) if best_scheme else best_cavity_data
best_key_info = best_scheme.get("key_info", {}) if best_scheme else best_key_info
# 8. 保存模具型腔数据(包含方案级预览链接)
await self.storage_service.save_mold_cavity_data(
db_session, stp_file_id, detailed_cavity_json
)
html_file_path = self.html_generator.generate_and_save_visualization(
geometry_data,
Path(file_path).name,
cavity_data=best_cavity_data,
pointcloud_data=pointcloud_data,
lod_data=lod_data,
)
await self.storage_service.save_html_file(
db_session,
stp_file_id,
Path(html_file_path).name,
html_file_path,
)
# 9. 分析模具设计
analysis_result = self.geometry_analyzer.analyze_mold_design(geometry_data)
if analysis_result:
await self.storage_service.save_features_and_recommendations(
db_session,
stp_file_id,
analysis_result.get("detected_features", []),
analysis_result.get("design_recommendations", []),
)
await self._save_analysis_metrics(db_session, stp_file_id, analysis_result)
# 9.6 更新STP文件的分析摘要字段
await self.storage_service.update_stp_file_analysis_summary(
db_session,
stp_file_id,
volume=geometry_data.get("volume", 0),
surface_area=geometry_data.get("surface_area", 0),
product_weight=CalculationService.calculate_product_weight(
geometry_data.get("volume", 0), selected_material["density"]
),
)
# 9.7 FreeCAD 几何验证
verification_result = await self._step_verify(
file_path, db_session, task_id, stp_file_id, analysis_result
)
# 9.8 LLM 增强分析(可选,不影响主流程)
llm_report = None
llm_parting = None
if analysis_result:
llm_report = await llm_service.generate_design_report(
analysis_result, detailed_cavity_json
)
candidate_schemes = detailed_cavity_json.get("candidate_schemes", [])
if candidate_schemes:
cavity_count = detailed_cavity_json.get("mold_cavities", {}).get("cavity_count", 1)
if isinstance(cavity_count, (int, float)):
cavity_count = int(cavity_count)
else:
cavity_count = 1
llm_parting = await llm_service.recommend_parting_direction(
geometry_data, candidate_schemes, selected_material,
cavity_count=cavity_count,
)
# 10. 完成处理
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "completed")
await self.storage_service.update_task_status(
db_session, task_id, "completed", 100, "模具型腔生成完成"
)
# 更新任务缓存状态
await redis_task_manager.update_task(task_id, {
"geometry_data": geometry_data,
"analysis_result": analysis_result,
"plan_result": detailed_cavity_json,
"candidate_schemes": detailed_cavity_json.get("candidate_schemes", []),
"best_scheme_id": detailed_cavity_json.get("best_scheme_id"),
"cavity_data": best_cavity_data,
"key_info": best_key_info,
"html_file": best_scheme.get("html_file", f"/html/{Path(html_file_path).name}") if best_scheme else f"/html/{Path(html_file_path).name}",
"verification": verification_result,
"llm_report": llm_report,
"llm_parting_recommendation": llm_parting,
"status": ProcessingStatus.COMPLETED,
"completed_at": str(datetime.now()),
})
logger.info(f"模具型腔生成完成: {task_id}")
logger.info(f"key_info metadata: {detailed_cavity_json.get('metadata', {})}")
logger.info(f"key_info manufacturing_info: {detailed_cavity_json.get('manufacturing_info', {})}")
logger.info(f"key_info geometric_characteristics: {detailed_cavity_json.get('mold_cavities', {}).get('cavity_key_info', {}).get('geometric_characteristics', {})}")
except Exception as e:
logger.error(f"模具型腔生成失败: {e}")
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "failed")
await self.storage_service.update_task_status(
db_session, task_id, "failed", error_message=str(e)
)
task = await redis_task_manager.get_task(task_id)
if task:
await redis_task_manager.update_task(task_id, {
"status": ProcessingStatus.FAILED,
"error": str(e),
"completed_at": str(datetime.now()),
})
# ─── 内部步骤 ───
async def _step_generate_mesh(
self, shape, geometry_data: dict, file_path: str,
db_session: AsyncSession, stp_file_id: int, task_id: str,
) -> Optional[Dict[str, Any]]:
"""生成网格数据并持久化,失败不影响主流程"""
mesh_result = None
try:
mesh_result = self.mesh_generator.generate_mesh_from_shape(shape)
vertices = mesh_result.get("vertices", [])
faces = mesh_result.get("faces", [])
points = mesh_result.get("points", [])
normals = mesh_result.get("normals", [])
point_count = mesh_result.get("point_count", 0)
vertex_count = mesh_result.get("vertex_count", 0)
face_count = mesh_result.get("face_count", 0)
if vertices and faces:
bbox = geometry_data.get("bounding_box", {})
mesh_json = {
"metadata": {
"file_name": Path(file_path).name,
"generated_at": datetime.now().isoformat(),
"quality": "medium",
"vertex_count": vertex_count,
"face_count": face_count,
"point_count": point_count,
},
"mesh": {
"vertices": vertices,
"faces": faces,
},
"pointcloud": {
"points": points,
"normals": normals,
"count": point_count,
},
"bounding_box": bbox,
}
await self.storage_service.save_mesh_data(
db_session,
stp_file_id=stp_file_id,
mesh_json=mesh_json,
quality="medium",
)
await redis_task_manager.update_task(task_id, {
"mesh_summary": {
"vertex_count": vertex_count,
"face_count": face_count,
"point_count": point_count,
"quality": "medium",
}
})
except Exception as mesh_err:
logger.warning(f"网格生成或保存失败,不影响主流程: {mesh_err}")
return mesh_result
async def _step_generate_cavity(
self, shape, selected_material: dict, is_foam_material: bool,
) -> Optional[Dict[str, Any]]:
"""生成多方案分模结果"""
plan_result = None
try:
if shape:
plan_result = self.multi_scheme_planner.generate_plan(
shape=shape,
material=selected_material,
is_foam_material=is_foam_material,
)
logger.info(
f"多方案分模完成: 生成 {len(plan_result.get('candidate_schemes', []))} 套方案"
)
except Exception as cavity_err:
logger.warning(f"多方案分模失败,使用简化数据: {cavity_err}")
traceback.print_exc()
plan_result = None
return plan_result
async def _step_verify(
self, file_path: str, db_session: AsyncSession,
task_id: str, stp_file_id: int, analysis_result: Optional[dict],
) -> Optional[Dict[str, Any]]:
"""FreeCAD 几何验证(可通过配置禁用)"""
from config.settings import settings
if not settings.ENABLE_FREECAD_VERIFICATION:
logger.info("FreeCAD验证已禁用(设置 ENABLE_FREECAD_VERIFICATION=true 启用)")
return {"status": "disabled", "reason": "FreeCAD验证已禁用"}
await self.storage_service.update_task_status(
db_session, task_id, "processing", 90, "FreeCAD几何验证"
)
try:
from services.verification_service import GeometryVerificationService
verification_svc = GeometryVerificationService(timeout=settings.FREECAD_VERIFICATION_TIMEOUT)
verification_result = await verification_svc.verify_stp_file(file_path)
if verification_result and analysis_result:
await self._save_verification_metrics(db_session, stp_file_id, verification_result)
logger.info(f"FreeCAD验证完成: {verification_result.get('status', 'unknown') if verification_result else 'failed'}")
return verification_result
except Exception as ve:
logger.warning(f"FreeCAD验证失败(不影响主流程): {ve}")
return {"status": "error", "error": str(ve)}
async def _attach_scheme_previews(
self,
detailed_cavity_json: Dict[str, Any],
geometry_data: Dict[str, Any],
stp_filename: str,
pointcloud_data: Optional[Dict[str, Any]] = None,
lod_data: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""为每个候选分模方案生成独立HTML预览链接。"""
candidate_schemes = detailed_cavity_json.get("candidate_schemes", [])
if not candidate_schemes:
return detailed_cavity_json
for scheme in candidate_schemes:
cavity_data = scheme.get("cavity_data")
if not cavity_data:
continue
suffix = scheme.get("scheme_id")
html_path = self.html_generator.generate_and_save_visualization(
geometry_data,
stp_filename,
cavity_data=cavity_data,
pointcloud_data=pointcloud_data,
suffix=suffix,
lod_data=lod_data,
)
scheme["html_file"] = f"/html/{Path(html_path).name}"
best_scheme = CalculationService.get_best_scheme(detailed_cavity_json)
if best_scheme:
detailed_cavity_json["html_file"] = best_scheme.get("html_file")
return detailed_cavity_json
# ─── 指标持久化 ───
async def _save_analysis_metrics(self, session: AsyncSession, stp_file_id: int, analysis_result: dict):
"""保存分析指标到数据库"""
from models.database import AnalysisMetrics
quality_metrics = analysis_result.get("quality_metrics", {})
analysis_summary = analysis_result.get("analysis_summary", "")
metrics = AnalysisMetrics(
stp_file_id=stp_file_id,
volume_utilization=quality_metrics.get("volume_utilization", 0),
topology_complexity=quality_metrics.get("topology_complexity", 0),
wall_uniformity=quality_metrics.get("wall_uniformity", 0),
analysis_summary=analysis_summary,
)
session.add(metrics)
await session.commit()
logger.info(f"分析指标保存成功: {metrics.id}")
async def _save_verification_metrics(self, session: AsyncSession, stp_file_id: int, verification_result: dict):
"""保存验证指标到数据库"""
from models.database import AnalysisMetrics
from sqlalchemy import select
result = await session.execute(
select(AnalysisMetrics).where(AnalysisMetrics.stp_file_id == stp_file_id)
)
metrics = result.scalar_one_or_none()
comparison = verification_result.get("comparison", {})
volume_comparison = comparison.get("volume", {})
area_comparison = comparison.get("surface_area", {})
if metrics:
metrics.verification_status = verification_result.get("status", "unknown")
metrics.verification_volume_diff = volume_comparison.get("difference_percent", 0)
metrics.verification_area_diff = area_comparison.get("difference_percent", 0)
metrics.verification_details = verification_result
else:
metrics = AnalysisMetrics(
stp_file_id=stp_file_id,
verification_status=verification_result.get("status", "unknown"),
verification_volume_diff=volume_comparison.get("difference_percent", 0),
verification_area_diff=area_comparison.get("difference_percent", 0),
verification_details=verification_result,
)
session.add(metrics)
await session.commit()
logger.info(f"验证指标保存成功: stp_file_id={stp_file_id}")
# 模块级单例,供路由层直接使用
processing_service = ProcessingService()