coo7 commited on
Commit
b5cfecc
·
verified ·
1 Parent(s): ed5f2a4

Create types/image.go

Browse files
Files changed (1) hide show
  1. internal/types/image.go +211 -0
internal/types/image.go ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package types
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "log"
7
+ "monica-proxy/internal/config"
8
+ "monica-proxy/internal/utils"
9
+ "net/http"
10
+ "strings"
11
+ "sync"
12
+ "time"
13
+
14
+ "github.com/cespare/xxhash/v2"
15
+ "github.com/google/uuid"
16
+ )
17
+
18
+ const MaxFileSize = 10 * 1024 * 1024 // 10MB
19
+
20
+ var imageCache sync.Map
21
+
22
+ // sampleAndHash 对base64字符串进行采样并计算xxHash
23
+ func sampleAndHash(data string) string {
24
+ // 如果数据长度小于1024,直接计算整个字符串的哈希
25
+ if len(data) <= 1024 {
26
+ return fmt.Sprintf("%x", xxhash.Sum64String(data))
27
+ }
28
+
29
+ // 采样策略:
30
+ // 1. 取前256字节
31
+ // 2. 取中间256字节
32
+ // 3. 取最后256字节
33
+ var samples []string
34
+ samples = append(samples, data[:256])
35
+ mid := len(data) / 2
36
+ samples = append(samples, data[mid-128:mid+128])
37
+ samples = append(samples, data[len(data)-256:])
38
+
39
+ // 将采样数据拼接后计算哈希
40
+ return fmt.Sprintf("%x", xxhash.Sum64String(strings.Join(samples, "")))
41
+ }
42
+
43
+ // UploadBase64Image 上传base64编码的图片到Monica
44
+ func UploadBase64Image(ctx context.Context, base64Data string) (*FileInfo, error) {
45
+ // 1. 生成缓存key
46
+ cacheKey := sampleAndHash(base64Data)
47
+
48
+ // 2. 检查缓存
49
+ if value, exists := imageCache.Load(cacheKey); exists {
50
+ return value.(*FileInfo), nil
51
+ }
52
+
53
+ // 3. 解析base64数据
54
+ // 移除 "data:image/png;base64," 这样的前缀
55
+ parts := strings.Split(base64Data, ",")
56
+ if len(parts) != 2 {
57
+ return nil, fmt.Errorf("invalid base64 image format")
58
+ }
59
+
60
+ // 获取图片类型
61
+ mimeType := strings.TrimSuffix(strings.TrimPrefix(parts[0], "data:"), ";base64")
62
+ if !strings.HasPrefix(mimeType, "image/") {
63
+ return nil, fmt.Errorf("invalid image mime type: %s", mimeType)
64
+ }
65
+
66
+ // 解码base64数据
67
+ imageData, err := utils.Base64Decode(parts[1])
68
+ if err != nil {
69
+ return nil, fmt.Errorf("decode base64 failed: %v", err)
70
+ }
71
+
72
+ // 4. 验证图片格式和大小
73
+ fileInfo, err := validateImageBytes(imageData, mimeType)
74
+ if err != nil {
75
+ return nil, fmt.Errorf("validate image failed: %v", err)
76
+ }
77
+ log.Printf("file info: %+v", fileInfo)
78
+
79
+ // 5. 获取预签名URL
80
+ preSignReq := &PreSignRequest{
81
+ FilenameList: []string{fileInfo.FileName},
82
+ Module: ImageModule,
83
+ Location: ImageLocation,
84
+ ObjID: uuid.New().String(),
85
+ }
86
+
87
+ var preSignResp PreSignResponse
88
+ _, err = utils.RestyDefaultClient.R().
89
+ SetContext(ctx).
90
+ SetHeader("cookie", config.MonicaConfig.MonicaCookie).
91
+ SetBody(preSignReq).
92
+ SetResult(&preSignResp).
93
+ Post(PreSignURL)
94
+
95
+ if err != nil {
96
+ return nil, fmt.Errorf("get pre-sign url failed: %v", err)
97
+ }
98
+
99
+ if len(preSignResp.Data.PreSignURLList) == 0 || len(preSignResp.Data.ObjectURLList) == 0 {
100
+ return nil, fmt.Errorf("no pre-sign url or object url returned")
101
+ }
102
+ log.Printf("preSign info: %+v", preSignResp)
103
+
104
+ // 6. 上传图片数据
105
+ _, err = utils.RestyDefaultClient.R().
106
+ SetContext(ctx).
107
+ SetHeader("Content-Type", fileInfo.FileType).
108
+ SetBody(imageData).
109
+ Put(preSignResp.Data.PreSignURLList[0])
110
+
111
+ if err != nil {
112
+ return nil, fmt.Errorf("upload file failed: %v", err)
113
+ }
114
+
115
+ // 7. 创建文件对象
116
+ fileInfo.ObjectURL = preSignResp.Data.ObjectURLList[0]
117
+ uploadReq := &FileUploadRequest{
118
+ Data: []FileInfo{*fileInfo},
119
+ }
120
+
121
+ var uploadResp FileUploadResponse
122
+ _, err = utils.RestyDefaultClient.R().
123
+ SetContext(ctx).
124
+ SetHeader("cookie", config.MonicaConfig.MonicaCookie).
125
+ SetBody(uploadReq).
126
+ SetResult(&uploadResp).
127
+ Post(FileUploadURL)
128
+
129
+ if err != nil {
130
+ return nil, fmt.Errorf("create file object failed: %v", err)
131
+ }
132
+ log.Printf("uploadResp: %+v", uploadResp)
133
+ if len(uploadResp.Data.Items) > 0 {
134
+ fileInfo.FileName = uploadResp.Data.Items[0].FileName
135
+ fileInfo.FileType = uploadResp.Data.Items[0].FileType
136
+ fileInfo.FileSize = uploadResp.Data.Items[0].FileSize
137
+ fileInfo.FileUID = uploadResp.Data.Items[0].FileUID
138
+ fileInfo.FileExt = uploadResp.Data.Items[0].FileType
139
+ fileInfo.FileTokens = uploadResp.Data.Items[0].FileTokens
140
+ fileInfo.FileChunks = uploadResp.Data.Items[0].FileChunks
141
+ }
142
+
143
+ fileInfo.UseFullText = true
144
+ fileInfo.FileURL = preSignResp.Data.CDNURLList[0]
145
+
146
+ // 8. 获取文件llm读取结果知道有返回
147
+ var batchResp FileBatchGetResponse
148
+ reqMap := make(map[string][]string)
149
+ reqMap["file_uids"] = []string{fileInfo.FileUID}
150
+ var retryCount = 1
151
+ for {
152
+ if retryCount > 5 {
153
+ return nil, fmt.Errorf("retry limit exceeded")
154
+ }
155
+ _, err = utils.RestyDefaultClient.R().
156
+ SetContext(ctx).
157
+ SetHeader("cookie", config.MonicaConfig.MonicaCookie).
158
+ SetBody(reqMap).
159
+ SetResult(&batchResp).
160
+ Post(FileGetURL)
161
+ if err != nil {
162
+ return nil, fmt.Errorf("batch get file failed: %v", err)
163
+ }
164
+ if len(batchResp.Data.Items) > 0 && batchResp.Data.Items[0].FileChunks > 0 {
165
+ break
166
+ } else {
167
+ retryCount++
168
+ }
169
+ time.Sleep(1 * time.Second)
170
+ }
171
+ fileInfo.FileChunks = batchResp.Data.Items[0].FileChunks
172
+ fileInfo.FileTokens = batchResp.Data.Items[0].FileTokens
173
+ fileInfo.URL = ""
174
+ fileInfo.ObjectURL = ""
175
+
176
+ // 9. 保存到缓存
177
+ imageCache.Store(cacheKey, fileInfo)
178
+
179
+ return fileInfo, nil
180
+ }
181
+
182
+ // validateImageBytes 验证图片字节数据的格式和大小
183
+ func validateImageBytes(imageData []byte, mimeType string) (*FileInfo, error) {
184
+ if len(imageData) > MaxFileSize {
185
+ return nil, fmt.Errorf("file size exceeds limit: %d > %d", len(imageData), MaxFileSize)
186
+ }
187
+
188
+ contentType := http.DetectContentType(imageData)
189
+ if !SupportedImageTypes[contentType] {
190
+ return nil, fmt.Errorf("unsupported image type: %s", contentType)
191
+ }
192
+
193
+ // 根据MIME类型生成文件扩展名
194
+ ext := ".png"
195
+ switch mimeType {
196
+ case "image/jpeg":
197
+ ext = ".jpg"
198
+ case "image/gif":
199
+ ext = ".gif"
200
+ case "image/webp":
201
+ ext = ".webp"
202
+ }
203
+
204
+ fileName := fmt.Sprintf("%s%s", uuid.New().String(), ext)
205
+
206
+ return &FileInfo{
207
+ FileName: fileName,
208
+ FileSize: int64(len(imageData)),
209
+ FileType: contentType,
210
+ }, nil
211
+ }