本项目采用 Go Code as Single Source of Truth 的理念,前端 TypeScript 类型从后端 Go Structs 自动生成。
这确保了:
- ✅ 前后端类型始终一致
- ✅ 减少手动维护成本
- ✅ 编译期类型检查
- ✅ AI 友好(Cursor 可以直接理解和修改)
传统的 IDL(Thrift/Protobuf)方案存在以下问题:
- 认知割裂:开发者需要在
.thrift/.proto和 Go 代码之间切换 - 上下文污染:生成的代码占据 AI 上下文窗口,降低 AI 理解能力
- 修改成本高:改 IDL → 重新生成 → 解决冲突 → 改 Go 代码
- 不符合 DDD:生成的代码分散在全局目录,破坏领域自包含性
- AI 难操作:AI 难以调用外部 CLI 工具(
hz update、protoc)
我们的方案:使用 Hertz 原生的 Go Struct + Tag,配合 tygo 自动生成 TypeScript 类型。
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 generateBackend (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 Composer (Cmd/Ctrl + I)
- 输入提示词:
读取 backend/domains/chat/http/dto/send_message.go,
在 frontend/src/types/domain/chat.ts 中生成对应的 TypeScript 接口。
保持字段名与 json tag 一致。
项目根目录的 .cursorrules 已经配置了类型同步规则。
只需要对 Cursor 说:"sync types chat"
AI 会自动:
- 读取
backend/domains/chat/http/dto/*.go - 提取 json tag
- 映射类型
- 生成
frontend/src/types/domain/chat.ts
| 用户输入 | AI 行为 |
|---|---|
| "sync types chat" | 同步聊天领域类型 |
| "sync types llm" | 同步 LLM 领域类型 |
| "sync types all" | 同步所有领域类型 |
-
修改后端 DTO:
// 添加新字段 type SendMessageRequest struct { // ... 现有字段 ConversationID string `json:"conversation_id,omitempty"` // 新增 }
-
Cursor 同步类型:
对 Cursor 说: "sync types chat" -
前端立即获得类型支持:
const request: SendMessageRequest = { user_id: "user-123", message: "Hello", conversation_id: "conv-456", // TypeScript 知道这个字段 };
-
运行脚本:
./scripts/sync_types.sh
-
检查差异:
git diff frontend/src/types/
-
提交变更:
git add frontend/src/types/ git commit -m "sync: update frontend types"
# .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 会失败在 .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."// ✅ 好的命名
type SendMessageRequest struct {}
type SendMessageResponse struct {}
// ❌ 不好的命名
type SendMsgReq struct {} // 缩写
type SMResp struct {} // 过度缩写// ✅ 好的 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"` // 缩写,不清晰
}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;
}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;
}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 并在前端添加验证)
检查:
- Go struct 的 json tag 是否正确
tygo.yaml的 type_mappings 是否配置- 运行
tygo generate -v查看详细日志
原因:忘记运行 tygo
解决:
./scripts/sync_types.sh原因:未安装 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 架构下的最佳前后端类型同步方案。