You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

8.4 KiB

前后端类型自动生成指南

解决前后端类型定义重复的问题


🎯 问题

当前前后端类型定义重复:

# 后端 (server/app/models/project.py)
class ProjectType(IntEnum):
    MINE = 1
    COLLAB = 2
// 前端 (client/src/types/project.ts)
export type ProjectType = 'mine' | 'collab';
export const PROJECT_TYPE = {
  MINE: 'mine',
  COLLAB: 'collab',
} as const;

问题

  • 重复定义,需要手动同步
  • 新增枚举值时容易遗漏
  • 维护成本高

解决方案:自动生成

方案 A:OpenAPI + TypeScript 生成器 推荐

1. 后端生成 OpenAPI Schema

FastAPI 自动生成 OpenAPI 文档:

# 访问 OpenAPI JSON
http://localhost:8000/openapi.json

2. 安装前端生成工具

cd client
npm install -D openapi-typescript-codegen
# 或
npm install -D @openapitools/openapi-generator-cli

3. 配置生成脚本

package.json

{
  "scripts": {
    "generate:types": "openapi-typescript-codegen --input http://localhost:8000/openapi.json --output ./src/types/generated --client axios"
  }
}

4. 生成类型

npm run generate:types

生成结果

// client/src/types/generated/models/ProjectType.ts
export enum ProjectType {
  MINE = 'mine',
  COLLAB = 'collab',
}

// client/src/types/generated/models/Project.ts
export interface Project {
  id: string;
  name: string;
  type: ProjectType;
  status: ProjectStatus;
  // ...
}

5. 使用生成的类型

// client/src/pages/WorkspacePage.tsx
import { ProjectType, Project } from '@/types/generated';

if (project.type === ProjectType.MINE) {
  // ...
}

优点

  • 100% 自动同步
  • 类型安全
  • 无需手动维护
  • API 变更自动反映

缺点

  • ⚠️ 需要后端服务运行
  • ⚠️ 需要额外构建步骤

方案 B:共享类型定义文件

1. 创建类型定义文件

// shared/types/project.ts
export const PROJECT_TYPE = {
  MINE: 'mine',
  COLLAB: 'collab',
} as const;

export type ProjectType = typeof PROJECT_TYPE[keyof typeof PROJECT_TYPE];

2. 后端和前端都引用

后端

# scripts/sync_types.py
import json

# 从 shared/types 读取类型定义
# 验证后端枚举是否一致

前端

// 直接导入
import { PROJECT_TYPE } from '@shared/types/project';

优点

  • 单一数据源
  • 手动控制

缺点

  • 需要额外的验证脚本
  • 跨语言支持复杂

方案 C:代码生成脚本

1. 编写生成脚本

# scripts/generate_frontend_types.py
"""
从后端 Model 自动生成前端 TypeScript 类型
"""
import ast
import re
from pathlib import Path

def extract_enums():
    """提取后端枚举定义"""
    model_file = Path("server/app/models/project.py")
    with open(model_file) as f:
        tree = ast.parse(f.read())
    
    enums = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef):
            if any(base.id == 'IntEnum' for base in node.bases if isinstance(base, ast.Name)):
                enum_name = node.name
                enum_values = {}
                for item in node.body:
                    if isinstance(item, ast.Assign):
                        name = item.targets[0].id
                        value = item.value.n
                        # 查找 to_string 映射
                        enum_values[name] = value
                enums[enum_name] = enum_values
    return enums

def generate_typescript(enums):
    """生成 TypeScript 类型定义"""
    output = []
    
    for enum_name, values in enums.items():
        # 生成类型
        type_values = [f"'{v.lower()}'" for v in values.keys()]
        output.append(f"export type {enum_name} = {' | '.join(type_values)};")
        output.append("")
        
        # 生成常量
        const_name = re.sub(r'(?<!^)(?=[A-Z])', '_', enum_name).upper()
        output.append(f"export const {const_name} = {{")
        for name in values.keys():
            output.append(f"  {name}: '{name.lower()}',")
        output.append("} as const;")
        output.append("")
    
    return "\n".join(output)

if __name__ == "__main__":
    enums = extract_enums()
    ts_code = generate_typescript(enums)
    
    output_file = Path("client/src/types/generated/enums.ts")
    output_file.parent.mkdir(exist_ok=True)
    output_file.write_text(ts_code)
    
    print(f"✅ Generated TypeScript types: {output_file}")

2. 配置自动运行

// package.json
{
  "scripts": {
    "predev": "python ../scripts/generate_frontend_types.py",
    "prebuild": "python ../scripts/generate_frontend_types.py"
  }
}

优点

  • 自动化
  • 无需外部工具
  • 可定制

缺点

  • ⚠️ 需要维护脚本
  • ⚠️ 可能需要处理复杂情况

🎯 推荐方案对比

方案 自动化程度 维护成本 类型安全 推荐指数
手动维护 手动 ⚠️ ⚠️ 中等
OpenAPI 生成 全自动
共享类型文件 ⚠️ 半自动 ⚠️ 中等
自定义脚本 自动 ⚠️ 中等

🚀 快速实施:OpenAPI 方案

步骤 1:确认 OpenAPI 可用

# 访问 OpenAPI JSON
curl http://localhost:8000/openapi.json | jq '.components.schemas'

步骤 2:安装生成工具

cd client
npm install -D openapi-typescript-codegen

步骤 3:添加生成脚本

// client/package.json
{
  "scripts": {
    "types:generate": "openapi --input http://localhost:8000/openapi.json --output ./src/types/generated --client axios",
    "types:watch": "npm run types:generate -- --watch"
  }
}

步骤 4:生成类型

npm run types:generate

步骤 5:使用生成的类型

// 使用生成的类型
import { ProjectType, ProjectStatus } from '@/types/generated';
import type { Project } from '@/types/generated';

// 现在枚举值和类型都是自动生成的!
if (project.type === ProjectType.MINE) {
  console.log('个人项目');
}

📋 完整示例

后端(保持不变)

# server/app/models/project.py
class ProjectType(IntEnum):
    """项目类型枚举"""
    MINE = 1
    COLLAB = 2
    
    @classmethod
    def to_string(cls, value: int) -> str:
        mapping = {cls.MINE: "mine", cls.COLLAB: "collab"}
        return mapping.get(value, "mine")

前端(自动生成)

运行生成

npm run types:generate

生成的文件 client/src/types/generated/models.ts

// 🤖 自动生成,请勿手动修改
export enum ProjectType {
  MINE = 'mine',
  COLLAB = 'collab',
}

export enum ProjectStatus {
  ACTIVE = 'active',
  ARCHIVED = 'archived',
  TRASHED = 'trashed',
  SOFT_DELETED = 'soft_deleted',
}

export interface Project {
  id: string;
  name: string;
  type: ProjectType;
  status: ProjectStatus;
  // ...
}

使用

import { ProjectType } from '@/types/generated/models';

// ✅ 100% 与后端同步
if (project.type === ProjectType.MINE) {
  // ...
}

💡 实用技巧

技巧 1:Git Hook 自动生成

# .husky/pre-commit
#!/bin/sh
cd client && npm run types:generate
git add src/types/generated

技巧 2:CI/CD 检查

# .github/workflows/ci.yml
- name: Check types sync
  run: |
    npm run types:generate
    git diff --exit-code src/types/generated || (echo "Types out of sync!" && exit 1)    

技巧 3:VSCode 任务

// .vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Generate Types",
      "type": "npm",
      "script": "types:generate",
      "problemMatcher": []
    }
  ]
}

🎯 最终建议

小型项目(枚举变化不频繁)

方案:手动维护 + 文档约定

  • 简单直接
  • 无需额外配置

中大型项目(推荐)

方案:OpenAPI + 自动生成

  • 完全自动化
  • 100% 同步
  • 行业标准

实施路径

  1. 立即:添加 OpenAPI 生成脚本
  2. 短期:配置 Git Hook 自动生成
  3. 长期:集成到 CI/CD

最后更新: 2026-02-06
维护者: Jointo 开发团队