QSSS_new / backend /scripts /sync_models.py
misonL's picture
特性:实现对话框、表单、输入框、标签、下拉菜单、分隔线、骨架屏、滑块、表格、文本区域的 UI 组件
c78ce9e
Raw
History Blame Contribute Delete
5.49 kB
import os
import re
import json
def parse_typescript_type(filepath):
"""解析 TypeScript 类型定义文件,提取接口/类型及其字段。"""
types = {}
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 匹配 interface 或 type 定义
matches = re.findall(
r'(?:export\s+)?(?:interface|type)\s+(\w+)\s*=?\s*\{([^}]+)\}',
content,
re.DOTALL)
for name, fields_str in matches:
fields = {}
# 匹配字段名和类型
field_matches = re.findall(r'(\w+)\s*:\s*([^;]+);', fields_str)
for field_name, field_type in field_matches:
fields[field_name.strip()] = field_type.strip()
types[name] = fields
return types
def parse_pydantic_schema(filepath):
"""解析 Pydantic schema 文件,提取模型及其字段。"""
schemas = {}
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 匹配 class 定义,继承 BaseModel 或其他 Pydantic 模型
matches = re.findall(
r'class\s+(\w+)\s*\(BaseModel(?:[^)]*)\):\s*([^`]+?)(?=\nclass|\n\ndef|\n\n@|\Z)',
content,
re.DOTALL)
for name, fields_str in matches:
fields = {}
# 匹配字段名和类型,考虑 Optional, List, Union 等
field_matches = re.findall(r'(\w+)\s*:\s*([^=\n]+)(?:=|\n)', fields_str)
for field_name, field_type in field_matches:
fields[field_name.strip()] = field_type.strip().replace(
'Field(...)', '').strip()
schemas[name] = fields
return schemas
def compare_models(frontend_types, backend_schemas):
"""对比前后端模型定义,报告不一致。"""
inconsistencies = []
# 比较前端类型和后端 schema
for fe_name, fe_fields in frontend_types.items():
# 尝试找到对应的后端 schema
be_name_candidates = [fe_name, fe_name.replace('I', '')] # 假设前端接口可能以I开头
matched_be_name = None
for candidate in be_name_candidates:
if candidate in backend_schemas:
matched_be_name = candidate
break
if not matched_be_name:
inconsistencies.append(f"前端类型 '{fe_name}' 在后端没有找到对应的 Schema。")
continue
be_fields = backend_schemas[matched_be_name]
# 检查前端字段是否都在后端存在
for fe_field_name, fe_field_type in fe_fields.items():
if fe_field_name not in be_fields:
inconsistencies.append(
f"模型 '{fe_name}' (前端) / '{matched_be_name}' (后端): 前端字段 '{fe_field_name}' 在后端缺失。")
# else:
# # 这里可以添加更复杂的类型对比逻辑
# pass
# 检查后端字段是否都在前端存在
for be_field_name, be_field_type in be_fields.items():
if be_field_name not in fe_fields:
inconsistencies.append(
f"模型 '{fe_name}' (前端) / '{matched_be_name}' (后端): 后端字段 '{be_field_name}' 在前端缺失。")
# else:
# # 这里可以添加更复杂的类型对比逻辑
# pass
# 检查后端 schema 是否都在前端有对应类型
for be_name, be_fields in backend_schemas.items():
fe_name_candidates = [be_name, f"I{be_name}"] # 假设前端接口可能以I开头
matched_fe_name = None
for candidate in fe_name_candidates:
if candidate in frontend_types:
matched_fe_name = candidate
break
if not matched_fe_name:
inconsistencies.append(f"后端 Schema '{be_name}' 在前端没有找到对应的类型。")
continue
return inconsistencies
def main():
frontend_types_dir = 'qsss-web/src/types'
backend_models_dir = 'backend/app/models'
backend_schemas_dir = 'backend/app/schemas'
all_frontend_types = {}
for filename in os.listdir(frontend_types_dir):
if filename.endswith('.ts'):
filepath = os.path.join(frontend_types_dir, filename)
all_frontend_types.update(parse_typescript_type(filepath))
all_backend_schemas = {}
# 优先解析 schemas 目录,因为它们通常是 API 的直接接口
for filename in os.listdir(backend_schemas_dir):
if filename.endswith('.py') and filename != '__init__.py':
filepath = os.path.join(backend_schemas_dir, filename)
all_backend_schemas.update(parse_pydantic_schema(filepath))
# 也可以解析 models 目录,但需要注意可能与 schemas 重复或包含ORM模型
# for filename in os.listdir(backend_models_dir):
# if filename.endswith('.py') and filename != '__init__.py':
# filepath = os.path.join(backend_models_dir, filename)
# all_backend_schemas.update(parse_pydantic_schema(filepath))
inconsistencies = compare_models(all_frontend_types, all_backend_schemas)
if inconsistencies:
print("发现前后端模型定义不一致:")
for item in inconsistencies:
print(f"- {item}")
print("\n请手动检查并修复这些不一致。")
else:
print("前后端模型定义一致。")
if __name__ == "__main__":
main()