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()