Initial commit

This commit is contained in:
2026-06-30 15:14:37 +08:00
commit 15dab96872
311 changed files with 95639 additions and 0 deletions
@@ -0,0 +1,486 @@
# MeshRay 前后端字段命名不一致问题排查与修复
**发现时间**: 2026-03-24
**问题等级**: 🔴 P1 - 功能异常
**影响范围**: Dashboard 统计、Network 列表
---
## 🐛 **问题总结**
### 核心问题
**前后端字段命名规范不统一**
| 层级 | 命名规范 | 示例 |
|------|----------|------|
| **后端 Model** | snake_case | `subnet_ipv4`, `mesh_mode`, `wg_mode` |
| **后端 DTO** | camelCase | `subnetIPv4`, `mode`, `wgMode` |
| **前端** | snake_case | `subnet_ipv4`, `mesh_mode`, `wg_mode` |
**结果**:
- ❌ 前端无法正确读取后端返回的数据
- ❌ Dashboard 统计卡片显示 0 或空白
- ❌ Network List 设备数不显示
---
## 🔍 **详细问题分析**
### 问题 1: Dashboard 统计数据
#### 后端代码(✅ 正确)
```go
// internal/api/handler/dashboard.go:30-49
func (h *DashboardHandler) GetStats(c *gin.Context) {
// ...
c.JSON(http.StatusOK, gin.H{
"data": gin.H{
"total_devices": deviceCount, // ✅ snake_case
"total_networks": networkCount, // ✅ snake_case
"online_devices": onlineCount, // ✅ snake_case
},
})
}
```
#### 前端期望(✅ 匹配)
```vue
<!-- web/src/views/Dashboard.vue -->
<div class="stat-value">{{ stats.total_networks }}</div>
<div class="stat-value">{{ stats.total_devices }}</div>
```
**结论**: Dashboard API 字段命名**一致**,这个问题已解决。
---
### 问题 2: Network List 缺少 device_count
#### 后端 Model(❌ 缺失)
```go
// internal/model/models.go:8-27
type Network struct {
ID uint64 `json:"id"`
Name string `json:"name"`
SubnetIPv4 string `json:"subnet_ipv4"` // ✅ snake_case
Mode string `json:"mesh_mode"` // ✅ snake_case
WGMode string `json:"wg_mode"` // ✅ snake_case
// ... 其他字段
Devices []Device `json:"devices,omitempty"` // 有关联,但没有计数
}
```
**问题**: Model 中没有 `device_count` 字段
---
#### 后端 DTO(❌ 错误)
```go
// internal/api/dto/responses.go:10-21
type NetworkResponse struct {
ID string `json:"id"`
Name string `json:"name"`
SubnetIPv4 string `json:"subnetIPv4"` // ❌ camelCase
Mode string `json:"mode"` // ❌ 不是 mesh_mode
WGMode string `json:"wgMode"` // ❌ camelCase
// ... 没有 device_count 字段
}
```
**问题**:
1. ❌ 使用 camelCase`subnetIPv4`, `wgMode`
2.`mode` 而不是 `mesh_mode`
3. ❌ 缺少 `device_count` 字段
---
#### 前端期望(✅ 正确)
```vue
<!-- web/src/views/Networks/List.vue -->
<el-table-column prop="subnet_ipv4" label="虚拟网段" />
<el-tag :type="row.mesh_mode === 'enhanced' ? 'success' : 'info'">
{{ row.mesh_mode === 'enhanced' ? '增强' : '原生' }}
</el-tag>
{{ row.device_count || 0 }}
```
**期望字段**:
-`subnet_ipv4` (snake_case)
-`mesh_mode` (snake_case)
-`device_count` (数字)
---
## ✅ **解决方案**
### 方案 A: 修改后端 DTO(推荐)
**优点**:
- ✅ 保持前端不变(前端已经是 snake_case
- ✅ 与后端 Model 命名一致
- ✅ 符合 RESTful API 最佳实践
**缺点**:
- ⚠️ 需要修改 DTO 结构体
- ⚠️ 需要添加计算逻辑
---
### 方案 B: 修改前端(不推荐)
**优点**:
- ✅ 后端改动小
**缺点**:
- ❌ 前端大量文件需要修改
- ❌ 违背 Go 语言蛇形命名惯例
- ❌ 工作量大
---
## 🔧 **实施步骤(采用方案 A)**
### Step 1: 修改 NetworkResponse DTO
**文件**: `internal/api/dto/responses.go`
```go
// NetworkResponse 网络响应 DTOID 为字符串格式,避免 JavaScript 精度丢失)
type NetworkResponse struct {
ID string `json:"id"` // ❄️ 雪花算法 ID(字符串格式)
Name string `json:"name"` // 组网名称
SubnetIPv4 string `json:"subnet_ipv4"` // ✅ IPv4 子网(snake_case
SubnetIPv6 string `json:"subnet_ipv6,omitempty"` // ✅ IPv6 子网
MeshMode string `json:"mesh_mode"` // ✅ 组网模式(snake_case
WGMode string `json:"wg_mode"` // ✅ WG 运行模式(snake_case
PolicyID string `json:"policy_id"` // ✅ ❄️ 关联策略 IDsnake_case
Status string `json:"status"` // 运行状态
DeviceCount int64 `json:"device_count"` // ✅ 新增:设备数量
Description string `json:"description,omitempty"` // 描述
CreatedAt string `json:"created_at"` // ✅ snake_case
UpdatedAt string `json:"updated_at"` // ✅ snake_case
}
```
---
### Step 2: 修改 ToNetworkResponse 函数
**文件**: `internal/api/dto/responses.go`
```go
// ToNetworkResponse Network 转 NetworkResponse
func ToNetworkResponse(network *model.Network) NetworkResponse {
// 计算设备数量
var deviceCount int64 = 0
if len(network.Devices) > 0 {
deviceCount = int64(len(network.Devices))
}
return NetworkResponse{
ID: fmt.Sprintf("%d", network.ID),
Name: network.Name,
SubnetIPv4: network.SubnetIPv4,
SubnetIPv6: network.SubnetIPv6,
MeshMode: network.Mode, // Mode → MeshMode
WGMode: network.WGMode,
PolicyID: fmt.Sprintf("%d", network.PolicyID),
Status: network.Status,
DeviceCount: deviceCount, // ✅ 新增
CreatedAt: network.CreatedAt.Format("2006-01-02T15:04:05Z"),
UpdatedAt: network.UpdatedAt.Format("2006-01-02T15:04:05Z"),
}
}
```
---
### Step 3: 确保 Service 层加载 Devices
**文件**: `internal/service/network.go`
检查 `ListNetworks` 方法是否预加载 Devices
```go
func (s *NetworkService) ListNetworks() ([]model.Network, error) {
var networks []model.Network
// ✅ 必须 Preload 加载 Devices
if err := s.store.DB().Preload("Devices").Find(&networks).Error; err != nil {
return nil, err
}
return networks, nil
}
```
---
### Step 4: 重新编译并测试
```bash
cd e:\Project\MeshRay
# 清理缓存
go clean -cache
# 编译
go build -o meshray.exe -ldflags "-s -w -H=windowsgui" ./cmd/meshray
# 启动服务
.\meshray.exe
```
---
## 🧪 **验证测试**
### 测试 1: Dashboard API
```bash
curl http://localhost:9531/api/v1/dashboard/stats \
-H "Authorization: Bearer <token>" | ConvertFrom-Json
# 预期输出:
# data.total_devices: 5
# data.total_networks: 3
# data.online_devices: 2
```
---
### 测试 2: Network List API
```bash
curl http://localhost:9531/api/v1/networks \
-H "Authorization: Bearer <token>" | ConvertFrom-Json
# 预期输出每个网络包含:
# - subnet_ipv4: "10.0.0.0/24"
# - mesh_mode: "enhanced"
# - wg_mode: "kernel"
# - device_count: 5
```
---
### 测试 3: 前端显示
访问 http://localhost:9531/networks
**预期效果**:
- ✅ 虚拟网段显示正常
- ✅ 组网模式标签正确(增强/原生)
- ✅ 设备数量显示正确
- ✅ 无 Console 错误
---
## 📊 **字段命名对照表**
### Network 相关
| 后端 Model | 后端 DTO(旧) | 后端 DTO(新) | 前端 | 状态 |
|------------|----------------|----------------|------|------|
| `subnet_ipv4` | `subnetIPv4` ❌ | `subnet_ipv4` ✅ | `subnet_ipv4` | 🔴 待修复 |
| `mode` | `mode` ❌ | `mesh_mode` ✅ | `mesh_mode` | 🔴 待修复 |
| `wg_mode` | `wgMode` ❌ | `wg_mode` ✅ | `wg_mode` | 🔴 待修复 |
| `policy_id` | `policyId` ❌ | `policy_id` ✅ | `policy_id` | 🔴 待修复 |
| - | - ❌ | `device_count` ✅ | `device_count` | 🔴 待添加 |
| `created_at` | `createdAt` ❌ | `created_at` ✅ | `created_at` | 🔴 待修复 |
| `updated_at` | `updatedAt` ❌ | `updated_at` ✅ | `updated_at` | 🔴 待修复 |
---
### Device 相关
| 后端 Model | 后端 DTO(旧) | 后端 DTO(新) | 前端 | 状态 |
|------------|----------------|----------------|------|------|
| `virtual_ip` | `virtualIP` ❌ | `virtual_ip` ✅ | `virtual_ip` | 🔴 待修复 |
| `public_key` | `publicKey` ❌ | `public_key` ✅ | `public_key` | 🔴 待修复 |
| `is_relay_capable` | `isRelayCapable` ❌ | `is_relay_capable` ✅ | `is_relay_capable` | 🔴 待修复 |
---
## 🎯 **命名规范原则**
### 后端(Go
-**JSON 标签使用 snake_case**
```go
type Model struct {
SubnetIPv4 string `json:"subnet_ipv4"` // ✅
MeshMode string `json:"mesh_mode"` // ✅
}
```
- ✅ **Go 字段使用 CamelCase**
```go
type Model struct {
SubnetIPv4 string // ✅ Go 语法要求
MeshMode string // ✅
}
```
---
### 前端(Vue/JS
- ✅ **使用 snake_case**
```javascript
const network = {
subnet_ipv4: "10.0.0.0/24", // ✅
mesh_mode: "enhanced", // ✅
device_count: 5 // ✅
}
```
---
### 为什么选择 snake_case
1. **RESTful API 标准**: JSON 通常使用 snake_case
2. **Go 语言惯例**: Go 的 JSON 标签推荐使用 snake_case
3. **跨语言兼容**: snake_case 在所有编程语言中都易读
4. **前端一致性**: Vue/React 项目中常用 snake_case
---
## 🐛 **常见错误**
### 错误 1: 混合使用命名规范
```go
// ❌ 错误示范
type Response struct {
UserID string `json:"userId"` // camelCase
UserName string `json:"user_name"` // snake_case
CreatedAt string `json:"createdAt"` // camelCase
}
```
**正确做法**:
```go
// ✅ 统一使用 snake_case
type Response struct {
UserID string `json:"user_id"`
UserName string `json:"user_name"`
CreatedAt string `json:"created_at"`
}
```
---
### 错误 2: 忘记 Preload 关联数据
```go
// ❌ 错误:不会加载 Devices
db.Find(&networks)
// network.Devices 为空
// ✅ 正确:预加载 Devices
db.Preload("Devices").Find(&networks)
```
---
### 错误 3: 忘记添加 device_count 计算
```go
// ❌ 错误:只返回空数组
return NetworkResponse{
Devices: network.Devices, // 前端需要手动计算长度
}
// ✅ 正确:直接提供计数
return NetworkResponse{
DeviceCount: int64(len(network.Devices)), // 前端直接使用
}
```
---
## 📝 **检查清单**
修复完成后检查:
- [ ] ✅ DTO 所有 JSON 标签改为 snake_case
- [ ] ✅ 添加 `device_count` 字段
- [ ] ✅ Service 层 Preload("Devices")
- [ ] ✅ ToNetworkResponse 计算 device_count
- [ ] ✅ Dashboard API 返回 snake_case
- [ ] ✅ 前端能正确读取 subnet_ipv4
- [ ] ✅ 前端能正确读取 mesh_mode
- [ ] ✅ 前端能正确读取 wg_mode
- [ ] ✅ Network List 显示设备数量
- [ ] ✅ 无 Console 错误
---
## 🎉 **预期效果**
### 修复前
```javascript
// Network API 返回
{
"data": [
{
"subnetIPv4": "10.0.0.0/24", // ❌ 前端无法识别
"mode": "enhanced", // ❌ 应该是 mesh_mode
"wgMode": "kernel" // ❌ 应该是 wg_mode
// ❌ 缺少 device_count
}
]
}
```
**前端表现**:
- ❌ 虚拟网段显示空白
- ❌ 模式标签不显示
- ❌ 设备数显示 0
---
### 修复后
```javascript
// Network API 返回
{
"data": [
{
"subnet_ipv4": "10.0.0.0/24", // ✅
"mesh_mode": "enhanced", // ✅
"wg_mode": "kernel", // ✅
"device_count": 5 // ✅
}
]
}
```
**前端表现**:
- ✅ 虚拟网段正常显示
- ✅ 模式标签正确(增强/原生)
- ✅ 设备数量正确显示
---
## 📚 **参考资料**
- [Go JSON 官方文档](https://golang.org/pkg/encoding/json/)
- [RESTful API 最佳实践](https://restfulapi.net/)
- [Vue.js 风格指南](https://vuejs.org/style-guide/)
---
**状态**: 🔴 **待修复**
**优先级**: P1 - 高优先级
**预计工作量**: 2 小时
*MeshRay - 细节决定成败,规范铸就品质!* ✨🔧