Spaces:
Paused
Paused
| package types | |
| import ( | |
| "context" | |
| "fmt" | |
| "monica-proxy/internal/config" | |
| "monica-proxy/internal/utils" | |
| "net/http" | |
| "strings" | |
| "sync" | |
| "time" | |
| "github.com/cespare/xxhash/v2" | |
| "github.com/google/uuid" | |
| ) | |
| const MaxFileSize = 10 * 1024 * 1024 // 10MB | |
| var imageCache sync.Map | |
| // sampleAndHash 对base64字符串进行采样并计算xxHash | |
| func sampleAndHash(data string) string { | |
| // 如果数据长度小于1024,直接计算整个字符串的哈希 | |
| if len(data) <= 1024 { | |
| return fmt.Sprintf("%x", xxhash.Sum64String(data)) | |
| } | |
| // 采样策略: | |
| // 1. 取前256字节 | |
| // 2. 取中间256字节 | |
| // 3. 取最后256字节 | |
| var samples []string | |
| samples = append(samples, data[:256]) | |
| mid := len(data) / 2 | |
| samples = append(samples, data[mid-128:mid+128]) | |
| samples = append(samples, data[len(data)-256:]) | |
| // 将采样数据拼接后计算哈希 | |
| return fmt.Sprintf("%x", xxhash.Sum64String(strings.Join(samples, ""))) | |
| } | |
| // UploadBase64Image 上传base64编码的图片到Monica | |
| func UploadBase64Image(ctx context.Context, base64Data string) (*FileInfo, error) { | |
| // 1. 生成缓存key | |
| cacheKey := sampleAndHash(base64Data) | |
| // 2. 检查缓存 | |
| if value, exists := imageCache.Load(cacheKey); exists { | |
| return value.(*FileInfo), nil | |
| } | |
| // 3. 解析base64数据 | |
| // 移除 "data:image/png;base64," 这样的前缀 | |
| parts := strings.Split(base64Data, ",") | |
| if len(parts) != 2 { | |
| return nil, fmt.Errorf("invalid base64 image format") | |
| } | |
| // 获取图片类型 | |
| mimeType := strings.TrimSuffix(strings.TrimPrefix(parts[0], "data:"), ";base64") | |
| if !strings.HasPrefix(mimeType, "image/") { | |
| return nil, fmt.Errorf("invalid image mime type: %s", mimeType) | |
| } | |
| // 解码base64数据 | |
| imageData, err := utils.Base64Decode(parts[1]) | |
| if err != nil { | |
| return nil, fmt.Errorf("decode base64 failed: %v", err) | |
| } | |
| // 4. 验证图片格式和大小 | |
| fileInfo, err := validateImageBytes(imageData, mimeType) | |
| if err != nil { | |
| return nil, fmt.Errorf("validate image failed: %v", err) | |
| } | |
| // log.Printf("file info: %+v", fileInfo) | |
| // 5. 获取预签名URL | |
| preSignReq := &PreSignRequest{ | |
| FilenameList: []string{fileInfo.FileName}, | |
| Module: ImageModule, | |
| Location: ImageLocation, | |
| ObjID: uuid.New().String(), | |
| } | |
| var preSignResp PreSignResponse | |
| _, err = utils.RestyDefaultClient.R(). | |
| SetContext(ctx). | |
| SetHeader("cookie", config.MonicaConfig.MonicaCookie). | |
| SetBody(preSignReq). | |
| SetResult(&preSignResp). | |
| Post(PreSignURL) | |
| if err != nil { | |
| return nil, fmt.Errorf("get pre-sign url failed: %v", err) | |
| } | |
| if len(preSignResp.Data.PreSignURLList) == 0 || len(preSignResp.Data.ObjectURLList) == 0 { | |
| return nil, fmt.Errorf("no pre-sign url or object url returned") | |
| } | |
| // log.Printf("preSign info: %+v", preSignResp) | |
| // 6. 上传图片数据 | |
| _, err = utils.RestyDefaultClient.R(). | |
| SetContext(ctx). | |
| SetHeader("Content-Type", fileInfo.FileType). | |
| SetBody(imageData). | |
| Put(preSignResp.Data.PreSignURLList[0]) | |
| if err != nil { | |
| return nil, fmt.Errorf("upload file failed: %v", err) | |
| } | |
| // 7. 创建文件对象 | |
| fileInfo.ObjectURL = preSignResp.Data.ObjectURLList[0] | |
| uploadReq := &FileUploadRequest{ | |
| Data: []FileInfo{*fileInfo}, | |
| } | |
| var uploadResp FileUploadResponse | |
| _, err = utils.RestyDefaultClient.R(). | |
| SetContext(ctx). | |
| SetHeader("cookie", config.MonicaConfig.MonicaCookie). | |
| SetBody(uploadReq). | |
| SetResult(&uploadResp). | |
| Post(FileUploadURL) | |
| if err != nil { | |
| return nil, fmt.Errorf("create file object failed: %v", err) | |
| } | |
| // log.Printf("uploadResp: %+v", uploadResp) | |
| if len(uploadResp.Data.Items) > 0 { | |
| fileInfo.FileName = uploadResp.Data.Items[0].FileName | |
| fileInfo.FileType = uploadResp.Data.Items[0].FileType | |
| fileInfo.FileSize = uploadResp.Data.Items[0].FileSize | |
| fileInfo.FileUID = uploadResp.Data.Items[0].FileUID | |
| fileInfo.FileExt = uploadResp.Data.Items[0].FileType | |
| fileInfo.FileTokens = uploadResp.Data.Items[0].FileTokens | |
| fileInfo.FileChunks = uploadResp.Data.Items[0].FileChunks | |
| } | |
| fileInfo.UseFullText = true | |
| fileInfo.FileURL = preSignResp.Data.CDNURLList[0] | |
| // 8. 获取文件llm读取结果知道有返回 | |
| var batchResp FileBatchGetResponse | |
| reqMap := make(map[string][]string) | |
| reqMap["file_uids"] = []string{fileInfo.FileUID} | |
| var retryCount = 1 | |
| for { | |
| if retryCount > 5 { | |
| return nil, fmt.Errorf("retry limit exceeded") | |
| } | |
| _, err = utils.RestyDefaultClient.R(). | |
| SetContext(ctx). | |
| SetHeader("cookie", config.MonicaConfig.MonicaCookie). | |
| SetBody(reqMap). | |
| SetResult(&batchResp). | |
| Post(FileGetURL) | |
| if err != nil { | |
| return nil, fmt.Errorf("batch get file failed: %v", err) | |
| } | |
| if len(batchResp.Data.Items) > 0 && batchResp.Data.Items[0].FileChunks > 0 { | |
| break | |
| } else { | |
| retryCount++ | |
| } | |
| time.Sleep(1 * time.Second) | |
| } | |
| fileInfo.FileChunks = batchResp.Data.Items[0].FileChunks | |
| fileInfo.FileTokens = batchResp.Data.Items[0].FileTokens | |
| fileInfo.URL = "" | |
| fileInfo.ObjectURL = "" | |
| // 9. 保存到缓存 | |
| imageCache.Store(cacheKey, fileInfo) | |
| return fileInfo, nil | |
| } | |
| // validateImageBytes 验证图片字节数据的格式和大小 | |
| func validateImageBytes(imageData []byte, mimeType string) (*FileInfo, error) { | |
| if len(imageData) > MaxFileSize { | |
| return nil, fmt.Errorf("file size exceeds limit: %d > %d", len(imageData), MaxFileSize) | |
| } | |
| contentType := http.DetectContentType(imageData) | |
| if !SupportedImageTypes[contentType] { | |
| return nil, fmt.Errorf("unsupported image type: %s", contentType) | |
| } | |
| // 根据MIME类型生成文件扩展名 | |
| ext := ".png" | |
| switch mimeType { | |
| case "image/jpeg": | |
| ext = ".jpg" | |
| case "image/gif": | |
| ext = ".gif" | |
| case "image/webp": | |
| ext = ".webp" | |
| } | |
| fileName := fmt.Sprintf("%s%s", uuid.New().String(), ext) | |
| return &FileInfo{ | |
| FileName: fileName, | |
| FileSize: int64(len(imageData)), | |
| FileType: contentType, | |
| }, nil | |
| } | |