| 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() |
| |
| 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() |
| |
| 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 = {} |
| |
| 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 = [] |
|
|
| |
| for fe_name, fe_fields in frontend_types.items(): |
| |
| be_name_candidates = [fe_name, fe_name.replace('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}' 在后端缺失。") |
| |
| |
| |
|
|
| |
| 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}' 在前端缺失。") |
| |
| |
| |
|
|
| |
| for be_name, be_fields in backend_schemas.items(): |
| fe_name_candidates = [be_name, f"I{be_name}"] |
| 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 = {} |
| |
| 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)) |
|
|
| |
| |
| |
| |
| |
|
|
| 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() |
|
|