Skip to content

Latest commit

 

History

History
457 lines (339 loc) · 9.66 KB

File metadata and controls

457 lines (339 loc) · 9.66 KB

前后端类型同步指南

概述

本项目采用 Go Code as Single Source of Truth 的理念,前端 TypeScript 类型从后端 Go Structs 自动生成。

这确保了:

  • ✅ 前后端类型始终一致
  • ✅ 减少手动维护成本
  • ✅ 编译期类型检查
  • ✅ AI 友好(Cursor 可以直接理解和修改)

为什么不使用 IDL?

传统的 IDL(Thrift/Protobuf)方案存在以下问题:

  1. 认知割裂:开发者需要在 .thrift/.proto 和 Go 代码之间切换
  2. 上下文污染:生成的代码占据 AI 上下文窗口,降低 AI 理解能力
  3. 修改成本高:改 IDL → 重新生成 → 解决冲突 → 改 Go 代码
  4. 不符合 DDD:生成的代码分散在全局目录,破坏领域自包含性
  5. AI 难操作:AI 难以调用外部 CLI 工具(hz updateprotoc

我们的方案:使用 Hertz 原生的 Go Struct + Tag,配合 tygo 自动生成 TypeScript 类型。

方案一:tygo 自动化生成

安装

go install github.com/gzuidhof/tygo@latest

配置

项目根目录的 tygo.yaml

packages:
  # 聊天领域
  - path: "backend/domains/chat/http/dto"
    output_path: "frontend/src/types/domain/chat.ts"
    type_mappings:
      "time.Time": "string"
      "github.com/shopspring/decimal.Decimal": "string"

  # LLM 领域
  - path: "backend/domains/llm/http/dto"
    output_path: "frontend/src/types/domain/llm.ts"

  # 监控领域
  - path: "backend/domains/monitoring/http/dto"
    output_path: "frontend/src/types/domain/monitoring.ts"

  # 共享类型
  - path: "backend/domains/shared/types"
    output_path: "frontend/src/types/shared.ts"

使用

# 同步所有类型
./scripts/sync_types.sh

# 或者直接运行 tygo
tygo generate

示例

Backend (backend/domains/chat/http/dto/send_message.go):

package dto

// SendMessageRequest 发送消息请求
type SendMessageRequest struct {
    // 用户 ID
    UserID string `json:"user_id" binding:"required"`
    
    // 消息内容
    Message string `json:"message" binding:"required,max=10000"`
    
    // 使用的模型(可选)
    Model string `json:"model,omitempty" binding:"oneof=gpt-4o gpt-3.5-turbo claude-3"`
    
    // 温度参数(可选)
    Temperature *float64 `json:"temperature,omitempty" binding:"omitempty,min=0,max=2"`
}

// SendMessageResponse 发送消息响应
type SendMessageResponse struct {
    MessageID string `json:"message_id"`
    Content   string `json:"content"`
    Model     string `json:"model"`
    Tokens    int    `json:"tokens"`
}

Generated Frontend (frontend/src/types/domain/chat.ts):

// Code generated by tygo. DO NOT EDIT.
// Source: backend/domains/chat/http/dto

/**
 * SendMessageRequest 发送消息请求
 */
export interface SendMessageRequest {
  /** 用户 ID */
  user_id: string;
  
  /** 消息内容 */
  message: string;
  
  /** 使用的模型(可选) */
  model?: string;
  
  /** 温度参数(可选) */
  temperature?: number;
}

/**
 * SendMessageResponse 发送消息响应
 */
export interface SendMessageResponse {
  message_id: string;
  content: string;
  model: string;
  tokens: number;
}

类型映射规则

Go 类型 TypeScript 类型
string string
int, int64, float64 number
bool boolean
time.Time string (配置为 ISO 8601)
[]T T[]
map[string]T Record<string, T>
*T (指针) T | undefined
struct interface

处理可选字段

在 Go 中使用指针表示可选字段:

type UserProfile struct {
    Name     string   `json:"name"`           // 必需
    Email    *string  `json:"email,omitempty"` // 可选
    Age      *int     `json:"age,omitempty"`   // 可选
}

生成的 TypeScript:

export interface UserProfile {
  name: string;
  email?: string;
  age?: number;
}

方案二:Cursor AI 实时生成

适合快速迭代和小规模修改。

使用方法

  1. 打开 Cursor Composer (Cmd/Ctrl + I)
  2. 输入提示词:
读取 backend/domains/chat/http/dto/send_message.go,
在 frontend/src/types/domain/chat.ts 中生成对应的 TypeScript 接口。
保持字段名与 json tag 一致。

配置 .cursorrules

项目根目录的 .cursorrules 已经配置了类型同步规则。

只需要对 Cursor 说:"sync types chat"

AI 会自动:

  1. 读取 backend/domains/chat/http/dto/*.go
  2. 提取 json tag
  3. 映射类型
  4. 生成 frontend/src/types/domain/chat.ts

快捷指令

用户输入 AI 行为
"sync types chat" 同步聊天领域类型
"sync types llm" 同步 LLM 领域类型
"sync types all" 同步所有领域类型

推荐工作流

日常开发(小规模修改)

  1. 修改后端 DTO

    // 添加新字段
    type SendMessageRequest struct {
        // ... 现有字段
        ConversationID string `json:"conversation_id,omitempty"` // 新增
    }
  2. Cursor 同步类型

    对 Cursor 说: "sync types chat"
    
  3. 前端立即获得类型支持

    const request: SendMessageRequest = {
        user_id: "user-123",
        message: "Hello",
        conversation_id: "conv-456", // TypeScript 知道这个字段
    };

大规模重构

  1. 运行脚本

    ./scripts/sync_types.sh
  2. 检查差异

    git diff frontend/src/types/
  3. 提交变更

    git add frontend/src/types/
    git commit -m "sync: update frontend types"

CI 集成

GitHub Actions 示例

# .github/workflows/ci.yml

name: CI

on: [push, pull_request]

jobs:
  check-types:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Install tygo
        run: go install github.com/gzuidhof/tygo@latest
      
      - name: Generate TypeScript types
        run: tygo generate
      
      - name: Check if types are in sync
        run: |
          git diff --exit-code frontend/src/types/
        # 如果前端类型不同步,CI 会失败

Pre-commit Hook

.git/hooks/pre-commit 中添加:

#!/bin/bash

echo "Checking if frontend types are in sync..."

# 运行 tygo
tygo generate

# 检查是否有变更
if ! git diff --exit-code --quiet frontend/src/types/; then
    echo "❌ Frontend types are out of sync!"
    echo "Run ./scripts/sync_types.sh and commit the changes."
    exit 1
fi

echo "✅ Frontend types are in sync."

最佳实践

1. DTO 命名规范

// ✅ 好的命名
type SendMessageRequest struct {}
type SendMessageResponse struct {}

// ❌ 不好的命名
type SendMsgReq struct {}  // 缩写
type SMResp struct {}      // 过度缩写

2. 使用语义化的 json tag

// ✅ 好的 tag
type User struct {
    ID        string `json:"user_id"`        // 清晰
    CreatedAt time.Time `json:"created_at"`  // snake_case
}

// ❌ 不好的 tag
type User struct {
    ID        string `json:"id"`      // 太简单,可能冲突
    CreatedAt time.Time `json:"ca"`   // 缩写,不清晰
}

3. 添加注释

Go 的注释会被 tygo 保留到 TypeScript 中:

// User 用户信息
type User struct {
    // 用户唯一标识
    ID string `json:"user_id"`
    
    // 用户昵称(可选)
    Nickname *string `json:"nickname,omitempty"`
}

生成的 TypeScript:

/**
 * User 用户信息
 */
export interface User {
  /** 用户唯一标识 */
  user_id: string;
  
  /** 用户昵称(可选) */
  nickname?: string;
}

4. 处理嵌套结构

type ChatMessage struct {
    ID      string    `json:"id"`
    Content string    `json:"content"`
    Author  UserInfo  `json:"author"`  // 嵌套
}

type UserInfo struct {
    ID   string `json:"user_id"`
    Name string `json:"name"`
}

tygo 会自动处理嵌套:

export interface ChatMessage {
  id: string;
  content: string;
  author: UserInfo;  // 自动引用
}

export interface UserInfo {
  user_id: string;
  name: string;
}

5. 处理枚举

Go 没有枚举,使用常量:

type MessageRole string

const (
    MessageRoleUser      MessageRole = "user"
    MessageRoleAssistant MessageRole = "assistant"
    MessageRoleSystem    MessageRole = "system"
)

TypeScript 可以使用 union type:

export type MessageRole = "user" | "assistant" | "system";

(需要手动定义,或者在 DTO 中使用 string 并在前端添加验证)

故障排查

Q: tygo 生成的类型不正确

检查

  1. Go struct 的 json tag 是否正确
  2. tygo.yaml 的 type_mappings 是否配置
  3. 运行 tygo generate -v 查看详细日志

Q: 前端类型和后端不匹配

原因:忘记运行 tygo

解决

./scripts/sync_types.sh

Q: CI 中 tygo 命令找不到

原因:未安装 tygo

解决:在 CI 配置中添加安装步骤:

- name: Install tygo
  run: go install github.com/gzuidhof/tygo@latest

总结

维度 手动维护 IDL (Thrift/Proto) tygo + Cursor
维护成本
开发体验
AI 友好度
类型安全 运行时 编译期 编译期
Source of Truth 不明确 IDL 文件 Go Code
DDD 兼容性

结论:tygo + Cursor 的组合是 Vibe Coding Friendly DDD 架构下的最佳前后端类型同步方案。