251 lines
6.0 KiB
Markdown
251 lines
6.0 KiB
Markdown
# 截图存储 Bug 修复说明
|
||
|
||
## 问题描述
|
||
|
||
在实施 Base64 截图存储方案后,发现了一个严重的 Bug:
|
||
|
||
```
|
||
错误信息: GET data:image/jpeg;base64:1 net::ERR_INVALID_URL
|
||
```
|
||
|
||
## 根本原因
|
||
|
||
### 问题分析
|
||
|
||
1. **数据存储方式**:使用逗号分隔的字符串存储多个截图
|
||
|
||
```go
|
||
screenshots := strings.Join(customer.Screenshots, ",")
|
||
```
|
||
|
||
2. **数据读取方式**:使用逗号分割字符串
|
||
|
||
```go
|
||
c.Screenshots = strings.Split(screenshots.String, ",")
|
||
```
|
||
|
||
3. **Base64 数据特点**:Base64 编码的字符串**本身不包含逗号**,但是...
|
||
|
||
4. **Data URL 格式**:`data:image/jpeg;base64,{base64_string}`
|
||
- 注意:**逗号**是 Data URL 格式的一部分!
|
||
- 格式:`data:{MIME类型};base64,{Base64数据}`
|
||
|
||
### 问题示例
|
||
|
||
假设有两张截图:
|
||
|
||
**原始数据**:
|
||
|
||
```json
|
||
[
|
||
"data:image/jpeg;base64,/9j/4AAQSkZJRg...",
|
||
"data:image/png;base64,iVBORw0KGgoAAAA..."
|
||
]
|
||
```
|
||
|
||
**存储到数据库**(使用逗号连接):
|
||
|
||
```
|
||
data:image/jpeg;base64,/9j/4AAQSkZJRg...,data:image/png;base64,iVBORw0KGgoAAAA...
|
||
```
|
||
|
||
**从数据库读取**(使用逗号分割):
|
||
|
||
```json
|
||
[
|
||
"data:image/jpeg;base64", // ❌ 被截断!
|
||
"/9j/4AAQSkZJRg...", // ❌ 不是有效的 Data URL
|
||
"data:image/png;base64", // ❌ 被截断!
|
||
"iVBORw0KGgoAAAA..." // ❌ 不是有效的 Data URL
|
||
]
|
||
```
|
||
|
||
**结果**:前端尝试加载 `data:image/jpeg;base64` 导致 `ERR_INVALID_URL` 错误!
|
||
|
||
## 解决方案
|
||
|
||
### 修改存储格式
|
||
|
||
从**逗号分隔字符串**改为 **JSON 数组**:
|
||
|
||
#### 修改前(错误)
|
||
|
||
```go
|
||
// 存储
|
||
screenshots := strings.Join(customer.Screenshots, ",")
|
||
|
||
// 读取
|
||
c.Screenshots = strings.Split(screenshots.String, ",")
|
||
```
|
||
|
||
#### 修改后(正确)
|
||
|
||
```go
|
||
// 存储
|
||
screenshotsJSON, err := json.Marshal(customer.Screenshots)
|
||
// 结果: ["data:image/jpeg;base64,...","data:image/png;base64,..."]
|
||
|
||
// 读取
|
||
var screenshotArray []string
|
||
json.Unmarshal([]byte(screenshots.String), &screenshotArray)
|
||
```
|
||
|
||
### 向后兼容
|
||
|
||
为了兼容旧数据(文件路径格式),添加了降级处理:
|
||
|
||
```go
|
||
if screenshots.Valid && screenshots.String != "" {
|
||
// 尝试解析为 JSON 数组
|
||
var screenshotArray []string
|
||
if err := json.Unmarshal([]byte(screenshots.String), &screenshotArray); err == nil {
|
||
c.Screenshots = screenshotArray
|
||
} else {
|
||
// 向后兼容:如果不是 JSON,尝试逗号分隔(旧格式)
|
||
c.Screenshots = strings.Split(screenshots.String, ",")
|
||
}
|
||
} else {
|
||
c.Screenshots = []string{}
|
||
}
|
||
```
|
||
|
||
## 修改文件清单
|
||
|
||
### `/internal/storage/mysql_customer_storage.go`
|
||
|
||
1. **添加导入**
|
||
|
||
```go
|
||
import "encoding/json"
|
||
```
|
||
|
||
2. **修改 `GetAllCustomers` 方法**
|
||
- 使用 `json.Unmarshal` 解析 screenshots 字段
|
||
- 添加向后兼容逻辑
|
||
|
||
3. **修改 `GetCustomerByID` 方法**
|
||
- 使用 `json.Unmarshal` 解析 screenshots 字段
|
||
- 添加向后兼容逻辑
|
||
|
||
4. **修改 `CreateCustomer` 方法**
|
||
- 使用 `json.Marshal` 序列化 screenshots 数组
|
||
|
||
5. **修改 `UpdateCustomer` 方法**
|
||
- 使用 `json.Marshal` 序列化 screenshots 数组
|
||
|
||
## 数据格式对比
|
||
|
||
### 旧格式(文件路径 - 使用逗号分隔)
|
||
|
||
```
|
||
数据库存储: /static/uploads/1.jpg,/static/uploads/2.png
|
||
解析结果: ["/static/uploads/1.jpg", "/static/uploads/2.png"]
|
||
状态: ✅ 正常(路径中没有逗号)
|
||
```
|
||
|
||
### 错误格式(Base64 - 使用逗号分隔)
|
||
|
||
```
|
||
数据库存储: data:image/jpeg;base64,abc...,data:image/png;base64,xyz...
|
||
解析结果: ["data:image/jpeg;base64", "abc...", "data:image/png;base64", "xyz..."]
|
||
状态: ❌ 错误(Data URL 被逗号截断)
|
||
```
|
||
|
||
### 新格式(JSON 数组)
|
||
|
||
```
|
||
数据库存储: ["data:image/jpeg;base64,abc...","data:image/png;base64,xyz..."]
|
||
解析结果: ["data:image/jpeg;base64,abc...", "data:image/png;base64,xyz..."]
|
||
状态: ✅ 正常(完整的 Data URL)
|
||
```
|
||
|
||
## 测试验证
|
||
|
||
### 1. 单元测试数据
|
||
|
||
```go
|
||
// 测试 JSON 序列化
|
||
screenshots := []string{
|
||
"data:image/jpeg;base64,/9j/4AAQSkZJRg...",
|
||
"data:image/png;base64,iVBORw0KGgoAAAA...",
|
||
}
|
||
|
||
json, _ := json.Marshal(screenshots)
|
||
fmt.Println(string(json))
|
||
// 输出: ["data:image/jpeg;base64,/9j/4AAQSkZJRg...","data:image/png;base64,iVBORw0KGgoAAAA..."]
|
||
|
||
// 测试 JSON 反序列化
|
||
var result []string
|
||
json.Unmarshal(json, &result)
|
||
fmt.Println(result)
|
||
// 输出: [data:image/jpeg;base64,/9j/4AAQSkZJRg... data:image/png;base64,iVBORw0KGgoAAAA...]
|
||
```
|
||
|
||
### 2. 集成测试
|
||
|
||
```bash
|
||
# 1. 上传截图
|
||
curl -X POST http://localhost:8081/api/upload \
|
||
-F "screenshots=@test.jpg"
|
||
|
||
# 2. 创建客户
|
||
curl -X POST http://localhost:8081/api/customers \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"customerName": "测试",
|
||
"screenshots": ["data:image/jpeg;base64,/9j/4AAQ..."]
|
||
}'
|
||
|
||
# 3. 查询客户
|
||
curl http://localhost:8081/api/customers/{id}
|
||
|
||
# 4. 验证数据库
|
||
mysql> SELECT screenshots FROM customers WHERE id = '{id}';
|
||
# 应该看到: ["data:image/jpeg;base64,/9j/4AAQ..."]
|
||
```
|
||
|
||
## 经验教训
|
||
|
||
### 1. 分隔符选择的重要性
|
||
|
||
当使用分隔符连接字符串时,必须确保:
|
||
|
||
- ✅ 分隔符不会出现在数据中
|
||
- ✅ 或者使用转义机制
|
||
- ✅ 或者使用结构化格式(如 JSON)
|
||
|
||
### 2. Data URL 格式
|
||
|
||
```
|
||
data:[<mediatype>][;base64],<data>
|
||
^ ^ ^
|
||
| | |
|
||
MIME类型 编码 逗号分隔符(重要!)
|
||
```
|
||
|
||
逗号是 Data URL 规范的一部分,不能用作数组分隔符!
|
||
|
||
### 3. 最佳实践
|
||
|
||
对于复杂数据结构,优先使用:
|
||
|
||
1. **JSON** - 结构化、标准、易于解析
|
||
2. **Protocol Buffers** - 高性能、类型安全
|
||
3. **避免自定义分隔符** - 容易出错
|
||
|
||
### 4. 向后兼容
|
||
|
||
在修改数据格式时,务必考虑:
|
||
|
||
- 现有数据的迁移
|
||
- 降级处理逻辑
|
||
- 渐进式升级策略
|
||
|
||
## 总结
|
||
|
||
这个 Bug 的根本原因是:**使用逗号分隔包含逗号的数据**。
|
||
|
||
修复方案:**使用 JSON 数组格式存储和解析数据**。
|
||
|
||
这是一个典型的**数据格式设计问题**,提醒我们在设计数据存储格式时,必须充分考虑数据的特性和边界情况。
|