333 lines
10 KiB
Markdown
333 lines
10 KiB
Markdown
|
|
# 无符号UUID实现方案设计
|
|||
|
|
|
|||
|
|
## 1. 概述
|
|||
|
|
|
|||
|
|
### 1.1 背景
|
|||
|
|
当前项目中UUID使用带连字符的标准格式(36位:`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)。为了与Yggdrasil协议更好的兼容并优化存储空间,需要实现无符号UUID(32位十六进制字符串)。
|
|||
|
|
|
|||
|
|
### 1.2 目标
|
|||
|
|
- 定义无符号UUID的格式规范
|
|||
|
|
- 设计UUID生成、保存、读取、返回的转换逻辑
|
|||
|
|
- 确保与Yggdrasil协议的兼容性
|
|||
|
|
- 提供向后兼容和数据迁移策略
|
|||
|
|
|
|||
|
|
### 1.3 无符号UUID格式
|
|||
|
|
- **格式**: 32位十六进制字符串(小写)
|
|||
|
|
- **示例**: `123e4567e89b12d3a456426614174000`
|
|||
|
|
- **数据库存储**: varchar(32)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 当前UUID使用分析
|
|||
|
|
|
|||
|
|
### 2.1 UUID使用场景
|
|||
|
|
|
|||
|
|
| 场景 | 文件位置 | 用途 |
|
|||
|
|
|------|----------|------|
|
|||
|
|
| Profile创建 | [`internal/service/profile_service.go:67`](internal/service/profile_service.go:67) | 生成Profile UUID |
|
|||
|
|
| Client创建 | [`internal/service/token_service_redis.go:76`](internal/service/token_service_redis.go:76) | 生成Client UUID |
|
|||
|
|
| 数据库存储 | [`internal/model/profile.go:10`](internal/model/profile.go:10) | Profile表主键 varchar(36) |
|
|||
|
|
| 数据库存储 | [`internal/model/client.go:8`](internal/model/client.go:8) | Client表主键 varchar(36) |
|
|||
|
|
| API返回 | [`internal/model/profile.go:34`](internal/model/profile.go:34) | ProfileResponse.JSON |
|
|||
|
|
| Yggdrasil序列化 | [`internal/service/yggdrasil_serialization_service.go:54`](internal/service/yggdrasil_serialization_service.go:54) | profileId字段 |
|
|||
|
|
| Yggdrasil序列化 | [`internal/service/yggdrasil_serialization_service.go:113`](internal/service/yggdrasil_serialization_service.go:113) | id字段 |
|
|||
|
|
|
|||
|
|
### 2.2 现有转换函数
|
|||
|
|
项目已在 [`pkg/utils/format.go`](pkg/utils/format.go) 中实现了 `FormatUUID` 函数:
|
|||
|
|
- 功能:32位字符串 → 带连字符的36位标准格式
|
|||
|
|
- 使用场景:Handler层接收参数后转换为标准格式
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 转换逻辑设计
|
|||
|
|
|
|||
|
|
### 3.1 核心转换函数
|
|||
|
|
|
|||
|
|
在 [`pkg/utils/format.go`](pkg/utils/format.go) 中扩展以下函数:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// FormatUUIDToNoDash 将带连字符的UUID转换为无符号UUID(移除连字符)
|
|||
|
|
// 输入: "123e4567-e89b-12d3-a456-426614174000"
|
|||
|
|
// 输出: "123e4567e89b12d3a456426614174000"
|
|||
|
|
func FormatUUIDToNoDash(uuid string) string
|
|||
|
|
|
|||
|
|
// FormatUUIDToDash 将无符号UUID转换为带连字符的标准格式
|
|||
|
|
// 输入: "123e4567e89b12d3a456426614174000"
|
|||
|
|
// 输出: "123e4567-e89b-12d3-a456-426614174000"
|
|||
|
|
func FormatUUIDToDash(uuid string) string
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 UUID生成策略
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// GenerateUUID 生成无符号UUID
|
|||
|
|
// 使用github.com/google/uuid库生成标准UUID,然后移除连字符
|
|||
|
|
func GenerateUUID() string {
|
|||
|
|
return uuid.New().String() // 默认生成带连字符的UUID
|
|||
|
|
// 移除连字符
|
|||
|
|
return strings.ReplaceAll(uuid.New().String(), "-", "")
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 数据流转设计
|
|||
|
|
|
|||
|
|
### 4.1 总体数据流
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
生成UUID (36位标准格式)
|
|||
|
|
↓
|
|||
|
|
移除连字符 (32位无符号UUID)
|
|||
|
|
↓
|
|||
|
|
存储到数据库 (varchar(32))
|
|||
|
|
↓
|
|||
|
|
读取时根据场景转换
|
|||
|
|
├─→ Yggdrasil API: 保持无符号格式
|
|||
|
|
└─→ 内部处理/调试: 转换为标准格式
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 各环节处理
|
|||
|
|
|
|||
|
|
| 环节 | 操作 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| **生成** | `uuid.New().String()` → 移除`-` | 保持与google/uuid库兼容 |
|
|||
|
|
| **保存** | 直接存储32位字符串 | 数据库字段改为varchar(32) |
|
|||
|
|
| **读取** | 直接读取 | 无需转换 |
|
|||
|
|
| **返回** | 根据API类型选择 | 见4.3节 |
|
|||
|
|
|
|||
|
|
### 4.3 API返回策略
|
|||
|
|
|
|||
|
|
| API类型 | 返回格式 | 原因 |
|
|||
|
|
|---------|----------|------|
|
|||
|
|
| Yggdrasil协议 | 32位无符号 | Mojang/Yggdrasil标准 |
|
|||
|
|
| 内部REST API | 保持存储格式 | 统一简洁 |
|
|||
|
|
| 调试/日志 | 可选转换 | 便于阅读 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 需要修改的文件
|
|||
|
|
|
|||
|
|
### 5.1 核心文件修改
|
|||
|
|
|
|||
|
|
| 文件 | 修改内容 |
|
|||
|
|
|------|----------|
|
|||
|
|
| [`pkg/utils/format.go`](pkg/utils/format.go) | 添加 `FormatUUIDToNoDash`、`FormatUUIDToDash`、`GenerateUUID` 函数 |
|
|||
|
|
| [`pkg/utils/format_test.go`](pkg/utils/format_test.go) | 添加新函数的单元测试 |
|
|||
|
|
| [`internal/model/profile.go`](internal/model/profile.go) | UUID字段类型改为varchar(32) |
|
|||
|
|
| [`internal/model/client.go`](internal/model/client.go) | UUID字段类型改为varchar(32) |
|
|||
|
|
|
|||
|
|
### 5.2 服务层修改
|
|||
|
|
|
|||
|
|
| 文件 | 修改内容 |
|
|||
|
|
|------|----------|
|
|||
|
|
| [`internal/service/profile_service.go`](internal/service/profile_service.go) | UUID生成改用新函数 |
|
|||
|
|
| [`internal/service/token_service_redis.go`](internal/service/token_service_redis.go) | UUID生成改用新函数 |
|
|||
|
|
|
|||
|
|
### 5.3 Handler层修改
|
|||
|
|
|
|||
|
|
| 文件 | 修改内容 |
|
|||
|
|
|------|----------|
|
|||
|
|
| [`internal/handler/yggdrasil_handler.go`](internal/handler/yggdrasil_handler.go) | Yggdrasil API返回无符号UUID |
|
|||
|
|
| [`internal/handler/profile_handler.go`](internal/handler/profile_handler.go) | 内部API返回无符号UUID |
|
|||
|
|
|
|||
|
|
### 5.4 序列化服务修改
|
|||
|
|
|
|||
|
|
| 文件 | 修改内容 |
|
|||
|
|
|------|----------|
|
|||
|
|
| [`internal/service/yggdrasil_serialization_service.go`](internal/service/yggdrasil_serialization_service.go) | profileId和id字段使用无符号UUID |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 实现步骤
|
|||
|
|
|
|||
|
|
### 6.1 第一步:扩展工具函数
|
|||
|
|
|
|||
|
|
在 [`pkg/utils/format.go`](pkg/utils/format.go) 中添加:
|
|||
|
|
|
|||
|
|
1. `FormatUUIDToNoDash(uuid string) string` - 移除连字符
|
|||
|
|
2. `FormatUUIDToDash(uuid string) string` - 添加连字符(现有函数增强)
|
|||
|
|
3. `GenerateUUID() string` - 生成无符号UUID
|
|||
|
|
|
|||
|
|
### 6.2 第二步:修改数据模型
|
|||
|
|
|
|||
|
|
1. 修改 [`internal/model/profile.go`](internal/model/profile.go):
|
|||
|
|
```go
|
|||
|
|
UUID string `gorm:"column:uuid;type:varchar(32);primaryKey" json:"uuid"`
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. 修改 [`internal/model/client.go`](internal/model/client.go):
|
|||
|
|
```go
|
|||
|
|
UUID string `gorm:"column:uuid;type:varchar(32);primaryKey" json:"uuid"`
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.3 第三步:修改服务层
|
|||
|
|
|
|||
|
|
1. 修改 [`internal/service/profile_service.go`](internal/service/profile_service.go:67):
|
|||
|
|
```go
|
|||
|
|
// 生成无符号UUID
|
|||
|
|
profileUUID := utils.GenerateUUID() // 或 uuid.New().String() 后转换
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. 修改 [`internal/service/token_service_redis.go`](internal/service/token_service_redis.go:76):
|
|||
|
|
```go
|
|||
|
|
clientUUID := utils.GenerateUUID()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.4 第四步:修改Handler层
|
|||
|
|
|
|||
|
|
1. 修改 [`internal/handler/yggdrasil_handler.go`](internal/handler/yggdrasil_handler.go):
|
|||
|
|
- 移除 `utils.FormatUUID()` 调用(因为存储的已经是无符号格式)
|
|||
|
|
- Yggdrasil API直接返回无符号UUID
|
|||
|
|
|
|||
|
|
2. 修改 [`internal/handler/profile_handler.go`](internal/handler/profile_handler.go):
|
|||
|
|
- 同样移除 `utils.FormatUUID()` 调用
|
|||
|
|
|
|||
|
|
### 6.第五步:修改序列化服务
|
|||
|
|
|
|||
|
|
修改 [`internal/service/yggdrasil_serialization_service.go`](internal/service/yggdrasil_serialization_service.go):
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// SerializeProfile - profileId使用无符号UUID
|
|||
|
|
"profileId": profile.UUID, // 直接使用存储的32位UUID
|
|||
|
|
|
|||
|
|
// SerializeProfile - id使用无符号UUID
|
|||
|
|
"id": profile.UUID, // 直接使用存储的32位UUID
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 向后兼容性设计
|
|||
|
|
|
|||
|
|
### 7.1 双格式支持(可选)
|
|||
|
|
|
|||
|
|
如果需要同时支持新旧客户端,可以:
|
|||
|
|
|
|||
|
|
1. **数据库字段保持varchar(36)** - 存储带连字符的格式
|
|||
|
|
2. **新增转换函数** - 在返回Yggdrasil响应时转换为无符号格式
|
|||
|
|
|
|||
|
|
### 7.2 推荐策略:渐进式迁移
|
|||
|
|
|
|||
|
|
1. **第一阶段**: 实现新代码,生成和存储无符号UUID
|
|||
|
|
2. **第二阶段**: 运行数据迁移脚本
|
|||
|
|
3. **第三阶段**: 移除旧格式支持
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 数据迁移策略
|
|||
|
|
|
|||
|
|
### 8.1 迁移脚本设计
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 将profiles表的UUID转换为无符号格式
|
|||
|
|
UPDATE profiles
|
|||
|
|
SET uuid = REPLACE(REPLACE(REPLACE(REPLACE(uuid, '-', ''),
|
|||
|
|
SUBSTRING(uuid, 9, 1), ''), '', ''), '', '')
|
|||
|
|
WHERE LENGTH(uuid) = 36;
|
|||
|
|
|
|||
|
|
-- 实际迁移SQL(分步骤)
|
|||
|
|
UPDATE profiles
|
|||
|
|
SET uuid = CONCAT(
|
|||
|
|
SUBSTRING(uuid, 1, 8),
|
|||
|
|
SUBSTRING(uuid, 10, 4),
|
|||
|
|
SUBSTRING(uuid, 15, 4),
|
|||
|
|
SUBSTRING(uuid, 20, 4),
|
|||
|
|
SUBSTRING(uuid, 25, 12)
|
|||
|
|
)
|
|||
|
|
WHERE LENGTH(uuid) = 36 AND uuid LIKE '%-%-%-%-%';
|
|||
|
|
|
|||
|
|
-- 同样迁移clients表
|
|||
|
|
UPDATE clients
|
|||
|
|
SET uuid = CONCAT(
|
|||
|
|
SUBSTRING(uuid, 1, 8),
|
|||
|
|
SUBSTRING(uuid, 10, 4),
|
|||
|
|
SUBSTRING(uuid, 15, 4),
|
|||
|
|
SUBSTRING(uuid, 20, 4),
|
|||
|
|
SUBSTRING(uuid, 25, 12)
|
|||
|
|
)
|
|||
|
|
WHERE LENGTH(uuid) = 36 AND uuid LIKE '%-%-%-%-%';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 迁移注意事项
|
|||
|
|
|
|||
|
|
1. **备份数据** - 迁移前务必备份数据库
|
|||
|
|
2. **停服迁移** - 建议在低峰期执行
|
|||
|
|
3. **验证迁移** - 迁移后验证数据完整性
|
|||
|
|
4. **回滚方案** - 准备回滚脚本
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 测试计划
|
|||
|
|
|
|||
|
|
### 9.1 单元测试
|
|||
|
|
|
|||
|
|
在 [`pkg/utils/format_test.go`](pkg/utils/format_test.go) 中添加:
|
|||
|
|
|
|||
|
|
1. `TestFormatUUIDToNoDash` - 测试移除连字符
|
|||
|
|
2. `TestFormatUUIDToDash` - 测试添加连字符
|
|||
|
|
3. `TestGenerateUUID` - 测试UUID生成(32位、唯一性)
|
|||
|
|
|
|||
|
|
### 9.2 集成测试
|
|||
|
|
|
|||
|
|
1. 创建Profile后验证数据库存储格式
|
|||
|
|
2. Yggdrasil认证流程验证UUID格式
|
|||
|
|
3. 批量操作(BatchUpdate/BatchDelete)验证UUID处理
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 注意事项
|
|||
|
|
|
|||
|
|
### 10.1 Yggdrasil协议兼容性
|
|||
|
|
|
|||
|
|
根据Yggdrasil/Mojang协议规范:
|
|||
|
|
- 玩家UUID应为32位无符号十六进制字符串
|
|||
|
|
- profileId字段应为小写
|
|||
|
|
- 与Minecraft客户端交互时使用此格式
|
|||
|
|
|
|||
|
|
### 10.2 性能考虑
|
|||
|
|
|
|||
|
|
- 新UUID生成无需额外转换(直接生成32位)
|
|||
|
|
- 数据库varchar(32)比varchar(36)节省约10%存储空间
|
|||
|
|
- 索引查询效率略有提升
|
|||
|
|
|
|||
|
|
### 10.3 安全性
|
|||
|
|
|
|||
|
|
- UUID应使用加密安全的随机数生成器
|
|||
|
|
- github.com/google/uuid库默认使用crypto/rand,安全可靠
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11. Mermaid数据流图
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A[uuid.New] --> B{需要格式}
|
|||
|
|
B -->|存储| C[FormatUUIDToNoDash]
|
|||
|
|
B -->|Yggdrasil返回| C
|
|||
|
|
B -->|内部API返回| D[直接使用]
|
|||
|
|
|
|||
|
|
C --> E[数据库 varchar(32)]
|
|||
|
|
D --> E
|
|||
|
|
|
|||
|
|
E --> F{读取场景}
|
|||
|
|
F -->|Yggdrasil API| G[保持无符号]
|
|||
|
|
F -->|调试/日志| H[FormatUUIDToDash]
|
|||
|
|
|
|||
|
|
G --> I[响应客户端]
|
|||
|
|
H --> J[控制台输出]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 12. 总结
|
|||
|
|
|
|||
|
|
本方案实现了以下目标:
|
|||
|
|
|
|||
|
|
1. **格式统一定义**: 32位无符号十六进制字符串
|
|||
|
|
2. **最小化修改**: 保持核心逻辑不变,仅调整格式转换
|
|||
|
|
3. **Yggdrasil兼容**: 符合Mojang协议的UUID格式要求
|
|||
|
|
4. **平滑迁移**: 支持渐进式数据迁移
|
|||
|
|
5. **测试覆盖**: 完整的单元测试和集成测试计划
|
|||
|
|
|
|||
|
|
方案实施后,项目将统一使用无符号UUID格式,提升与Yggdrasil协议的兼容性,并优化存储空间。
|