340 lines
12 KiB
Markdown
340 lines
12 KiB
Markdown
# OCDP 命名约定对照表
|
||
|
||
## 快速参考
|
||
|
||
| 层级 | 变量/属性 | 类型名 | JSON 字段 |
|
||
|-----|----------|-------|----------|
|
||
| **Backend Go** | 导出: `PascalCase`<br>不导出: `camelCase` | `PascalCase` | `snake_case` |
|
||
| **OpenAPI Schema** | `snake_case` | `PascalCase` | `snake_case` |
|
||
| **Frontend Generated** | `snake_case` (引号) | `PascalCase` | `snake_case` |
|
||
| **Frontend Internal** | `camelCase` | `PascalCase` | `snake_case` |
|
||
|
||
---
|
||
|
||
## 详细说明
|
||
|
||
### 1. Backend (Go)
|
||
|
||
```go
|
||
// 文件: backend/internal/adapter/input/http/dto/cluster_dto.go
|
||
|
||
type ClusterResponse struct {
|
||
ID string `json:"id"` // 导出字段: PascalCase, JSON: snake_case
|
||
Name string `json:"name"`
|
||
HasCAData bool `json:"has_ca_data"` // Go: PascalCase → JSON: snake_case
|
||
CreatedAt string `json:"created_at"`
|
||
}
|
||
|
||
// 内部变量
|
||
func example() {
|
||
var clusterId string // 不导出: camelCase
|
||
var clusterName string
|
||
}
|
||
```
|
||
|
||
**规则**:
|
||
- ✅ 导出变量/字段: `PascalCase` (首字母大写)
|
||
- ✅ 不导出变量/字段: `camelCase` (首字母小写)
|
||
- ✅ 类型名: `PascalCase`
|
||
- ✅ JSON 标签: `snake_case`
|
||
|
||
---
|
||
|
||
### 2. OpenAPI 规范 (openapi.yaml)
|
||
|
||
```yaml
|
||
# 文件: backend/docs/openapi.yaml
|
||
|
||
components:
|
||
schemas:
|
||
ClusterResponse: # Schema 名称: PascalCase
|
||
type: object
|
||
properties:
|
||
id: # 属性: snake_case
|
||
type: string
|
||
name:
|
||
type: string
|
||
has_ca_data: # 属性: snake_case (与 Go JSON 标签一致)
|
||
type: boolean
|
||
created_at: # 属性: snake_case
|
||
type: string
|
||
```
|
||
|
||
**规则**:
|
||
- ✅ 固定字段 (operationId, paths, etc.): `camelCase`
|
||
- ✅ Schema 本身: `PascalCase`
|
||
- ✅ Schema 下面的属性: `snake_case`
|
||
|
||
---
|
||
|
||
### 3. Frontend TypeScript - 生成的 API Client
|
||
|
||
```typescript
|
||
// 文件: frontend/src/api/generated/models/cluster-response.ts
|
||
// 自动生成,不要手动修改
|
||
|
||
export interface ClusterResponse {
|
||
'id'?: string; // 属性: snake_case (加引号)
|
||
'name'?: string;
|
||
'has_ca_data'?: boolean; // 保持 snake_case,与 JSON 一致
|
||
'created_at'?: string;
|
||
}
|
||
```
|
||
|
||
**规则**:
|
||
- ✅ 类型名: `PascalCase`
|
||
- ✅ 属性: `snake_case` (带引号)
|
||
- ⚠️ 不要手动修改生成的文件
|
||
|
||
---
|
||
|
||
### 4. Frontend TypeScript - 内部类型
|
||
|
||
```typescript
|
||
// 文件: frontend/src/core/types/index.ts
|
||
// 前端内部使用的类型定义
|
||
|
||
export interface Cluster {
|
||
id: string; // 内部变量: camelCase
|
||
name: string;
|
||
hasCAData?: boolean; // camelCase (前端惯例)
|
||
hasCertData?: boolean;
|
||
createdAt: string; // camelCase
|
||
updatedAt: string;
|
||
}
|
||
|
||
// API 请求类型 (保持与后端一致)
|
||
export interface CreateClusterRequest {
|
||
name: string;
|
||
host: string;
|
||
ca_data: string; // JSON 字段: snake_case
|
||
cert_data: string; // 与后端 API 保持一致
|
||
key_data: string;
|
||
}
|
||
```
|
||
|
||
**规则**:
|
||
- ✅ 内部变量: `camelCase`
|
||
- ✅ 类型名: `PascalCase`
|
||
- ✅ JSON 序列化 (API 通信): `snake_case`
|
||
|
||
---
|
||
|
||
## 数据流转示例
|
||
|
||
### 完整的请求-响应流程
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 1. 前端组件 (camelCase) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ const cluster = { │
|
||
│ name: "Production", │
|
||
│ hasCAData: true, // camelCase │
|
||
│ createdAt: "2025-11-10" │
|
||
│ } │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 2. API Request Body (snake_case JSON) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ { │
|
||
│ "name": "Production", │
|
||
│ "ca_data": "LS0t...", // snake_case │
|
||
│ "cert_data": "LS0t..." │
|
||
│ } │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 3. Backend Go 结构体 (PascalCase) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ type CreateClusterRequest struct { │
|
||
│ Name string `json:"name"` │
|
||
│ CAData string `json:"ca_data"` // Go: PascalCase │
|
||
│ CertData string `json:"cert_data"` // JSON: snake_case │
|
||
│ } │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 4. API Response JSON (snake_case) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ { │
|
||
│ "id": "cluster-123", │
|
||
│ "name": "Production", │
|
||
│ "has_ca_data": true, // snake_case │
|
||
│ "created_at": "2025-11-10T08:00:00Z" │
|
||
│ } │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 5. 前端接收 (可以保持 snake_case 或转换为 camelCase) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ // 选项 A: 直接使用生成的类型 (snake_case) │
|
||
│ const cluster: ClusterResponse = response; │
|
||
│ console.log(cluster.has_ca_data); │
|
||
│ │
|
||
│ // 选项 B: 转换为内部类型 (camelCase) │
|
||
│ const cluster: Cluster = { │
|
||
│ id: response.id, │
|
||
│ hasCAData: response.has_ca_data, // 转换 │
|
||
│ createdAt: response.created_at // 转换 │
|
||
│ }; │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 命名转换对照
|
||
|
||
### 常见字段名转换
|
||
|
||
| Go (PascalCase) | JSON (snake_case) | TS Generated | TS Internal (camelCase) |
|
||
|----------------|------------------|--------------|------------------------|
|
||
| `ID` | `id` | `'id'` | `id` |
|
||
| `Name` | `name` | `'name'` | `name` |
|
||
| `ClusterID` | `cluster_id` | `'cluster_id'` | `clusterId` |
|
||
| `RegistryID` | `registry_id` | `'registry_id'` | `registryId` |
|
||
| `HasCAData` | `has_ca_data` | `'has_ca_data'` | `hasCAData` |
|
||
| `CAData` | `ca_data` | `'ca_data'` | `caData` |
|
||
| `CertData` | `cert_data` | `'cert_data'` | `certData` |
|
||
| `KeyData` | `key_data` | `'key_data'` | `keyData` |
|
||
| `CreatedAt` | `created_at` | `'created_at'` | `createdAt` |
|
||
| `UpdatedAt` | `updated_at` | `'updated_at'` | `updatedAt` |
|
||
|
||
---
|
||
|
||
## 重新生成 OpenAPI Client
|
||
|
||
### 安装依赖
|
||
|
||
```bash
|
||
# 安装 Java (如果尚未安装)
|
||
sudo apt-get install openjdk-11-jdk
|
||
|
||
# 安装 OpenAPI Generator CLI (全局)
|
||
npm install -g @openapitools/openapi-generator-cli
|
||
```
|
||
|
||
### 生成命令
|
||
|
||
```bash
|
||
# 方式 1: 使用项目根目录的 Makefile (推荐)
|
||
cd /home/mango/workspace/ocdp-go
|
||
make openapi-gen-frontend
|
||
|
||
# 方式 2: 使用前端目录的 npm 脚本
|
||
cd /home/mango/workspace/ocdp-go/frontend
|
||
npm run openapi-gen
|
||
|
||
# 方式 3: 直接运行 (如果需要自定义参数)
|
||
cd /home/mango/workspace/ocdp-go
|
||
openapi-generator-cli generate \
|
||
-i backend/docs/openapi.yaml \
|
||
-g typescript-axios \
|
||
-o frontend/src/api/generated \
|
||
--additional-properties=supportsES6=true,withSeparateModelsAndApi=true,apiPackage=api,modelPackage=models
|
||
```
|
||
|
||
### 文件权限问题解决
|
||
|
||
如果遇到权限问题 (文件属于 root):
|
||
|
||
```bash
|
||
# 修改生成文件的所有权
|
||
sudo chown -R $USER:$USER frontend/src/api/generated
|
||
|
||
# 然后重新生成
|
||
make openapi-gen-frontend
|
||
```
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### ✅ 推荐做法
|
||
|
||
1. **使用生成的类型进行 API 通信**
|
||
```typescript
|
||
import type { ClusterResponse } from "@/api/generated";
|
||
const clusters = await apiRequest<ClusterResponse[]>("/v1/clusters");
|
||
```
|
||
|
||
2. **统一使用 apiRequest helper**
|
||
```typescript
|
||
import { apiRequest } from "@/shared/utils/api-helpers";
|
||
// 自动处理认证、错误、token 刷新
|
||
```
|
||
|
||
3. **后端修改 OpenAPI 后,重新生成前端 client**
|
||
```bash
|
||
make openapi-gen-frontend
|
||
```
|
||
|
||
### ❌ 避免的做法
|
||
|
||
1. **不要手动修改生成的代码**
|
||
```typescript
|
||
// ❌ 不要修改 /frontend/src/api/generated/ 下的文件
|
||
// 这些文件会在重新生成时被覆盖
|
||
```
|
||
|
||
2. **不要直接使用 fetch**
|
||
```typescript
|
||
// ❌ 不推荐
|
||
const response = await fetch("/api/v1/clusters");
|
||
|
||
// ✅ 推荐
|
||
const clusters = await apiRequest("/v1/clusters");
|
||
```
|
||
|
||
3. **避免混淆命名约定**
|
||
```typescript
|
||
// ❌ 不要在 API 请求中使用 camelCase
|
||
const request = {
|
||
name: "Test",
|
||
caData: "xxx", // 错误! 应该是 ca_data
|
||
};
|
||
|
||
// ✅ 正确
|
||
const request = {
|
||
name: "Test",
|
||
ca_data: "xxx", // 与后端 JSON 标签一致
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 快速检查清单
|
||
|
||
### Backend (Go)
|
||
|
||
- [ ] 导出字段使用 `PascalCase`
|
||
- [ ] JSON 标签使用 `snake_case`
|
||
- [ ] 更新 OpenAPI 规范与代码保持一致
|
||
|
||
### OpenAPI 规范
|
||
|
||
- [ ] Schema 名称使用 `PascalCase`
|
||
- [ ] 属性使用 `snake_case`
|
||
- [ ] 与后端 DTO 的 JSON 标签一致
|
||
|
||
### Frontend
|
||
|
||
- [ ] 从 OpenAPI 重新生成 client
|
||
- [ ] 使用生成的类型进行 API 通信
|
||
- [ ] 内部类型可以使用 `camelCase` (可选)
|
||
- [ ] 使用 `apiRequest` helper
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- [API Client 详细说明](./frontend/API_CLIENT_CONVENTIONS.md)
|
||
- [OpenAPI 规范](./backend/docs/openapi.yaml)
|
||
- [前端 API Helper](./frontend/src/shared/utils/api-helpers.ts)
|
||
|
||
---
|
||
|
||
**更新日期**: 2025-11-10
|
||
|