This commit is contained in:
mangomqy
2025-11-13 02:54:06 +00:00
commit c5e51ed069
254 changed files with 54901 additions and 0 deletions

View File

@ -0,0 +1,458 @@
# Go vs TypeScript + class-transformer 对比
## 命名约定和自动转换实现
本文档展示 Go 和 TypeScript 如何实现类似的自动类型转换机制。
---
## 📋 命名约定总结
### OpenAPI 规范
| 元素 | 命名约定 | 示例 |
|------|---------|------|
| Fixed Fields | `camelCase` | `operationId`, `requestBody` |
| Schema 名称 | `PascalCase` | `ClusterResponse`, `CreateClusterRequest` |
| Schema 属性 | `snake_case` | `has_ca_data`, `created_at`, `cluster_id` |
### Backend Go
| 元素 | 命名约定 | 示例 |
|------|---------|------|
| 导出变量/字段 | `PascalCase` | `HasCAData`, `CreatedAt` |
| 非导出变量 | `camelCase` | `hasCAData`, `createdAt` |
| 类型名 | `PascalCase` | `ClusterResponse`, `CreateClusterRequest` |
| JSON 标签 | `snake_case` | `json:"has_ca_data"`, `json:"created_at"` |
### Frontend TypeScript
| 元素 | 命名约定 | 示例 |
|------|---------|------|
| 变量 | `camelCase` | `hasCAData`, `createdAt` |
| 类型名 | `PascalCase` | `Cluster`, `CreateClusterRequest` |
| JSON | `snake_case` | `has_ca_data`, `created_at` |
---
## 🔄 自动转换对比
### Go 的实现
```go
// backend/internal/adapter/input/http/dto/cluster_dto.go
package dto
// 类型定义PascalCase
type ClusterResponse struct {
// 导出字段PascalCase
ID string `json:"id"`
Name string `json:"name"`
Host string `json:"host"`
// 字段名PascalCase, JSONsnake_case
HasCAData bool `json:"has_ca_data"` // ← struct tag
HasCertData bool `json:"has_cert_data"` // ← struct tag
HasKeyData bool `json:"has_key_data"` // ← struct tag
CAData string `json:"ca_data"` // ← struct tag
CertData string `json:"cert_data"` // ← struct tag
KeyData string `json:"key_data"` // ← struct tag
CreatedAt string `json:"created_at"` // ← struct tag
UpdatedAt string `json:"updated_at"` // ← struct tag
}
// 使用 - Go 自动转换
func GetCluster() ClusterResponse {
cluster := ClusterResponse{
ID: "cluster-123",
Name: "Production",
HasCAData: true, // 内部使用 PascalCase
CAData: "••••••••",
CreatedAt: "2025-11-10",
}
// json.Marshal 自动转换为 snake_case
// {"id":"cluster-123","has_ca_data":true,"ca_data":"••••••••","created_at":"2025-11-10"}
return cluster
}
```
### TypeScript + class-transformer 的实现
```typescript
// frontend/src/api/models/cluster.model.ts
import { Expose } from 'class-transformer';
// 类型定义PascalCase
export class Cluster {
@Expose()
id!: string;
@Expose()
name!: string;
@Expose()
host!: string;
// 字段名camelCase, JSONsnake_case
@Expose({ name: 'has_ca_data' }) // ← @Expose decorator (类似 struct tag)
hasCAData?: boolean;
@Expose({ name: 'has_cert_data' }) // ← @Expose decorator
hasCertData?: boolean;
@Expose({ name: 'has_key_data' }) // ← @Expose decorator
hasKeyData?: boolean;
@Expose({ name: 'ca_data' }) // ← @Expose decorator
caData?: string;
@Expose({ name: 'cert_data' }) // ← @Expose decorator
certData?: string;
@Expose({ name: 'key_data' }) // ← @Expose decorator
keyData?: string;
@Expose({ name: 'created_at' }) // ← @Expose decorator
createdAt!: string;
@Expose({ name: 'updated_at' }) // ← @Expose decorator
updatedAt!: string;
}
// 使用 - TypeScript + class-transformer 自动转换
import { fromJson, toJson } from '@/api/serializer';
function getCluster(): Cluster {
// JSON → 类实例 (snake_case → camelCase)
const apiResponse = {
id: "cluster-123",
name: "Production",
has_ca_data: true, // JSON: snake_case
ca_data: "••••••••",
created_at: "2025-11-10",
};
const cluster = fromJson(Cluster, apiResponse);
// 内部使用 camelCase
console.log(cluster.hasCAData); // true
console.log(cluster.caData); // "••••••••"
console.log(cluster.createdAt); // "2025-11-10"
return cluster;
}
```
---
## 🔍 详细对比
### 1. 结构定义
#### Go
```go
type ClusterResponse struct {
HasCAData bool `json:"has_ca_data"`
CreatedAt string `json:"created_at"`
}
```
#### TypeScript + class-transformer
```typescript
class Cluster {
@Expose({ name: 'has_ca_data' })
hasCAData?: boolean;
@Expose({ name: 'created_at' })
createdAt!: string;
}
```
**对应关系**:
- Go 的 `struct tag` ↔ TypeScript 的 `@Expose` 装饰器
- Go 的 `json:"field_name"` ↔ TypeScript 的 `{ name: 'field_name' }`
---
### 2. JSON 序列化(结构体/类 → JSON
#### Go
```go
cluster := ClusterResponse{
HasCAData: true, // PascalCase
CreatedAt: "2025-11-10",
}
jsonBytes, _ := json.Marshal(cluster)
// 自动转换为: {"has_ca_data":true,"created_at":"2025-11-10"}
```
#### TypeScript + class-transformer
```typescript
const cluster = new Cluster();
cluster.hasCAData = true; // camelCase
cluster.createdAt = "2025-11-10";
const json = toJson(cluster);
// 自动转换为: {has_ca_data: true, created_at: "2025-11-10"}
```
**对应关系**:
- Go 的 `json.Marshal()` ↔ TypeScript 的 `toJson()`
- 都实现了:内部字段名 → JSON snake_case
---
### 3. JSON 反序列化JSON → 结构体/类)
#### Go
```go
jsonStr := `{"has_ca_data":true,"created_at":"2025-11-10"}`
var cluster ClusterResponse
json.Unmarshal([]byte(jsonStr), &cluster)
// 自动映射到 PascalCase 字段
fmt.Println(cluster.HasCAData) // true
fmt.Println(cluster.CreatedAt) // "2025-11-10"
```
#### TypeScript + class-transformer
```typescript
const apiResponse = {
has_ca_data: true,
created_at: "2025-11-10"
};
const cluster = fromJson(Cluster, apiResponse);
// 自动映射到 camelCase 字段
console.log(cluster.hasCAData); // true
console.log(cluster.createdAt); // "2025-11-10"
```
**对应关系**:
- Go 的 `json.Unmarshal()` ↔ TypeScript 的 `fromJson()`
- 都实现了JSON snake_case → 内部字段名
---
## 📊 完整数据流转示例
### Scenario: 创建集群
```
┌─────────────────────────────────────────────────────────────────┐
│ 1. Frontend 组件 (TypeScript camelCase) │
├─────────────────────────────────────────────────────────────────┤
│ const request = new CreateClusterRequest(); │
│ request.name = "Production"; │
│ request.caData = "LS0t..."; // camelCase │
│ request.certData = "LS0t..."; │
└─────────────────────────────────────────────────────────────────┘
↓ toJson(request)
┌─────────────────────────────────────────────────────────────────┐
│ 2. HTTP Request Body (JSON snake_case) │
├─────────────────────────────────────────────────────────────────┤
│ { │
│ "name": "Production", │
│ "ca_data": "LS0t...", // snake_case │
│ "cert_data": "LS0t..." │
│ } │
└─────────────────────────────────────────────────────────────────┘
↓ HTTP POST
┌─────────────────────────────────────────────────────────────────┐
│ 3. Backend Go (PascalCase struct) │
├─────────────────────────────────────────────────────────────────┤
│ type CreateClusterRequest struct { │
│ Name string `json:"name"` │
│ CAData string `json:"ca_data"` // PascalCase │
│ CertData string `json:"cert_data"` │
│ } │
│ │
│ // json.Unmarshal 自动映射 │
│ var req CreateClusterRequest │
│ json.Unmarshal(body, &req) │
│ // req.CAData = "LS0t..." // 自动转换! │
└─────────────────────────────────────────────────────────────────┘
↓ 处理业务逻辑
┌─────────────────────────────────────────────────────────────────┐
│ 4. Backend Response (JSON snake_case) │
├─────────────────────────────────────────────────────────────────┤
│ { │
│ "id": "cluster-123", │
│ "name": "Production", │
│ "has_ca_data": true, // snake_case │
│ "created_at": "2025-11-10T08:00:00Z" │
│ } │
└─────────────────────────────────────────────────────────────────┘
↓ fromJson(Cluster, response)
┌─────────────────────────────────────────────────────────────────┐
│ 5. Frontend 使用 (TypeScript camelCase) │
├─────────────────────────────────────────────────────────────────┤
│ const cluster: Cluster = await createCluster(request); │
│ │
│ // 使用 camelCase │
│ console.log(cluster.hasCAData); // true │
│ console.log(cluster.createdAt); // "2025-11-10T08:00:00Z" │
│ │
│ // 在 React 组件中 │
│ {cluster.hasCAData && <Badge>Has CA</Badge>} │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🎯 关键相似点
| 特性 | Go | TypeScript + class-transformer |
|------|----|---------------------------------|
| **元数据标记** | `struct tags` | `@Expose` 装饰器 |
| **内部命名** | `PascalCase` | `camelCase` |
| **JSON 命名** | `snake_case` | `snake_case` |
| **序列化** | `json.Marshal()` | `toJson()` |
| **反序列化** | `json.Unmarshal()` | `fromJson()` |
| **自动转换** | ✅ 内置支持 | ✅ 通过 class-transformer |
---
## 🔧 实现代码对比
### Go - 完整示例
```go
package dto
import "encoding/json"
type ClusterResponse struct {
ID string `json:"id"`
Name string `json:"name"`
HasCAData bool `json:"has_ca_data"`
CreatedAt string `json:"created_at"`
}
func main() {
// 创建实例
cluster := ClusterResponse{
ID: "123",
Name: "Test",
HasCAData: true,
CreatedAt: "2025-11-10",
}
// 序列化
jsonBytes, _ := json.Marshal(cluster)
// Output: {"id":"123","name":"Test","has_ca_data":true,"created_at":"2025-11-10"}
// 反序列化
var newCluster ClusterResponse
json.Unmarshal(jsonBytes, &newCluster)
// newCluster.HasCAData = true
}
```
### TypeScript - 完整示例
```typescript
import { Expose } from 'class-transformer';
import { fromJson, toJson } from '@/api/serializer';
class Cluster {
@Expose() id!: string;
@Expose() name!: string;
@Expose({ name: 'has_ca_data' }) hasCAData?: boolean;
@Expose({ name: 'created_at' }) createdAt!: string;
}
function main() {
// 创建实例
const cluster = new Cluster();
cluster.id = "123";
cluster.name = "Test";
cluster.hasCAData = true;
cluster.createdAt = "2025-11-10";
// 序列化
const json = toJson(cluster);
// Output: {id:"123",name:"Test",has_ca_data:true,created_at:"2025-11-10"}
// 反序列化
const newCluster = fromJson(Cluster, json);
// newCluster.hasCAData === true
}
```
---
## 📝 OpenAPI 驱动开发
### OpenAPI 规范 → Go
```yaml
# backend/docs/openapi.yaml
components:
schemas:
ClusterResponse: # → type ClusterResponse struct
properties:
id: # → ID string `json:"id"`
type: string
has_ca_data: # → HasCAData bool `json:"has_ca_data"`
type: boolean
created_at: # → CreatedAt string `json:"created_at"`
type: string
```
### OpenAPI 规范 → TypeScript
```yaml
# backend/docs/openapi.yaml
components:
schemas:
ClusterResponse: # → class Cluster
properties:
id: # → @Expose() id!: string
type: string
has_ca_data: # → @Expose({ name: 'has_ca_data' }) hasCAData?: boolean
type: boolean
created_at: # → @Expose({ name: 'created_at' }) createdAt!: string
type: string
```
---
## ✨ 总结
### Go 的优势
- ✅ 内置支持,无需额外库
- ✅ 编译时生成代码
- ✅ 零运行时开销
### TypeScript + class-transformer 的优势
- ✅ 与 Go 相似的开发体验
- ✅ 装饰器语法清晰
- ✅ 类型安全
- ✅ 运行时开销极小
### 共同点
- ✅ 都使用元数据标记字段映射
- ✅ 都实现了自动类型转换
- ✅ 都保持了代码的可读性和可维护性
- ✅ 都支持 OpenAPI 驱动开发
---
**结论**: TypeScript + class-transformer 成功复现了 Go 的 struct tags 机制,为前端开发提供了同样优雅的类型转换体验!
---
**创建日期**: 2025-11-10
**作者**: AI Assistant