@@ -36,11 +36,44 @@ func LoadFileSource(c *gin.Context, source *types.FileSource, reason ...string)
3636 return nil , fmt .Errorf ("file source is nil" )
3737 }
3838
39- // 如果已有缓存,直接返回
39+ if common .DebugEnabled {
40+ logger .LogDebug (c , fmt .Sprintf ("LoadFileSource starting for: %s" , source .GetIdentifier ()))
41+ }
42+
43+ // 1. 快速检查内部缓存
4044 if source .HasCache () {
45+ // 即使命中内部缓存,也要确保注册到清理列表(如果尚未注册)
46+ if c != nil {
47+ registerSourceForCleanup (c , source )
48+ }
4149 return source .GetCache (), nil
4250 }
4351
52+ // 2. 加锁保护加载过程
53+ source .Mu ().Lock ()
54+ defer source .Mu ().Unlock ()
55+
56+ // 3. 双重检查
57+ if source .HasCache () {
58+ if c != nil {
59+ registerSourceForCleanup (c , source )
60+ }
61+ return source .GetCache (), nil
62+ }
63+
64+ // 4. 如果是 URL,检查 Context 缓存
65+ var contextKey string
66+ if source .IsURL () && c != nil {
67+ contextKey = getContextCacheKey (source .URL )
68+ if cachedData , exists := c .Get (contextKey ); exists {
69+ data := cachedData .(* types.CachedFileData )
70+ source .SetCache (data )
71+ registerSourceForCleanup (c , source )
72+ return data , nil
73+ }
74+ }
75+
76+ // 5. 执行加载逻辑
4477 var cachedData * types.CachedFileData
4578 var err error
4679
@@ -54,10 +87,13 @@ func LoadFileSource(c *gin.Context, source *types.FileSource, reason ...string)
5487 return nil , err
5588 }
5689
57- // 设置缓存
90+ // 6. 设置缓存
5891 source .SetCache (cachedData )
92+ if contextKey != "" && c != nil {
93+ c .Set (contextKey , cachedData )
94+ }
5995
60- // 注册到 context 以便请求结束时自动清理
96+ // 7. 注册到 context 以便请求结束时自动清理
6197 if c != nil {
6298 registerSourceForCleanup (c , source )
6399 }
@@ -67,13 +103,18 @@ func LoadFileSource(c *gin.Context, source *types.FileSource, reason ...string)
67103
68104// registerSourceForCleanup 注册 FileSource 到 context 以便请求结束时清理
69105func registerSourceForCleanup (c * gin.Context , source * types.FileSource ) {
106+ if source .IsRegistered () {
107+ return
108+ }
109+
70110 key := string (constant .ContextKeyFileSourcesToCleanup )
71111 var sources []* types.FileSource
72112 if existing , exists := c .Get (key ); exists {
73113 sources = existing .([]* types.FileSource )
74114 }
75115 sources = append (sources , source )
76116 c .Set (key , sources )
117+ source .SetRegistered (true )
77118}
78119
79120// CleanupFileSources 清理请求中所有注册的 FileSource
@@ -83,9 +124,6 @@ func CleanupFileSources(c *gin.Context) {
83124 if sources , exists := c .Get (key ); exists {
84125 for _ , source := range sources .([]* types.FileSource ) {
85126 if cache := source .GetCache (); cache != nil {
86- if cache .IsDisk () {
87- common .DecrementDiskFiles (cache .Size )
88- }
89127 cache .Close ()
90128 }
91129 }
@@ -94,21 +132,13 @@ func CleanupFileSources(c *gin.Context) {
94132}
95133
96134// loadFromURL 从 URL 加载文件
97- // 支持磁盘缓存:当文件大小超过阈值且磁盘缓存可用时,将数据存储到磁盘
98135func loadFromURL (c * gin.Context , url string , reason ... string ) (* types.CachedFileData , error ) {
99- contextKey := getContextCacheKey (url )
100-
101- // 检查 context 缓存
102- if cachedData , exists := c .Get (contextKey ); exists {
103- if common .DebugEnabled {
104- logger .LogDebug (c , fmt .Sprintf ("Using cached file data for URL: %s" , url ))
105- }
106- return cachedData .(* types.CachedFileData ), nil
107- }
108-
109136 // 下载文件
110137 var maxFileSize = constant .MaxFileDownloadMB * 1024 * 1024
111138
139+ if common .DebugEnabled {
140+ logger .LogDebug (c , "loadFromURL: initiating download" )
141+ }
112142 resp , err := DoDownloadRequest (url , reason ... )
113143 if err != nil {
114144 return nil , fmt .Errorf ("failed to download file from %s: %w" , url , err )
@@ -120,6 +150,9 @@ func loadFromURL(c *gin.Context, url string, reason ...string) (*types.CachedFil
120150 }
121151
122152 // 读取文件内容(限制大小)
153+ if common .DebugEnabled {
154+ logger .LogDebug (c , "loadFromURL: reading response body" )
155+ }
123156 fileBytes , err := io .ReadAll (io .LimitReader (resp .Body , int64 (maxFileSize + 1 )))
124157 if err != nil {
125158 return nil , fmt .Errorf ("failed to read file content: %w" , err )
@@ -147,6 +180,10 @@ func loadFromURL(c *gin.Context, url string, reason ...string) (*types.CachedFil
147180 cachedData = types .NewMemoryCachedData (base64Data , mimeType , int64 (len (fileBytes )))
148181 } else {
149182 cachedData = types .NewDiskCachedData (diskPath , mimeType , int64 (len (fileBytes )))
183+ cachedData .DiskSize = base64Size
184+ cachedData .OnClose = func (size int64 ) {
185+ common .DecrementDiskFiles (size )
186+ }
150187 common .IncrementDiskFiles (base64Size )
151188 if common .DebugEnabled {
152189 logger .LogDebug (c , fmt .Sprintf ("File cached to disk: %s, size: %d bytes" , diskPath , base64Size ))
@@ -159,6 +196,9 @@ func loadFromURL(c *gin.Context, url string, reason ...string) (*types.CachedFil
159196
160197 // 如果是图片,尝试获取图片配置
161198 if strings .HasPrefix (mimeType , "image/" ) {
199+ if common .DebugEnabled {
200+ logger .LogDebug (c , "loadFromURL: decoding image config" )
201+ }
162202 config , format , err := decodeImageConfig (fileBytes )
163203 if err == nil {
164204 cachedData .ImageConfig = & config
@@ -170,9 +210,6 @@ func loadFromURL(c *gin.Context, url string, reason ...string) (*types.CachedFil
170210 }
171211 }
172212
173- // 存入 context 缓存
174- c .Set (contextKey , cachedData )
175-
176213 return cachedData , nil
177214}
178215
@@ -187,7 +224,6 @@ func writeToDiskCache(base64Data string) (string, error) {
187224}
188225
189226// smartDetectMimeType 智能检测 MIME 类型
190- // 优先级:Content-Type header > Content-Disposition filename > URL 路径 > 内容嗅探 > 图片解码
191227func smartDetectMimeType (resp * http.Response , url string , fileBytes []byte ) string {
192228 // 1. 尝试从 Content-Type header 获取
193229 mimeType := resp .Header .Get ("Content-Type" )
@@ -259,13 +295,11 @@ func loadFromBase64(base64String string, providedMimeType string) (*types.Cached
259295
260296 // 处理 data: 前缀
261297 if strings .HasPrefix (base64String , "data:" ) {
262- // 格式: data:mime/type;base64,xxxxx
263298 idx := strings .Index (base64String , "," )
264299 if idx != - 1 {
265300 header := base64String [:idx ]
266301 cleanBase64 = base64String [idx + 1 :]
267302
268- // 从 header 提取 MIME 类型
269303 if strings .Contains (header , ":" ) && strings .Contains (header , ";" ) {
270304 mimeStart := strings .Index (header , ":" ) + 1
271305 mimeEnd := strings .Index (header , ";" )
@@ -280,36 +314,34 @@ func loadFromBase64(base64String string, providedMimeType string) (*types.Cached
280314 cleanBase64 = base64String
281315 }
282316
283- // 使用提供的 MIME 类型(如果有)
284317 if providedMimeType != "" {
285318 mimeType = providedMimeType
286319 }
287320
288- // 解码 base64
289321 decodedData , err := base64 .StdEncoding .DecodeString (cleanBase64 )
290322 if err != nil {
291323 return nil , fmt .Errorf ("failed to decode base64 data: %w" , err )
292324 }
293325
294- // 判断是否使用磁盘缓存(对于 base64 内联数据也支持磁盘缓存)
295326 base64Size := int64 (len (cleanBase64 ))
296327 var cachedData * types.CachedFileData
297328
298329 if shouldUseDiskCache (base64Size ) {
299- // 使用磁盘缓存
300330 diskPath , err := writeToDiskCache (cleanBase64 )
301331 if err != nil {
302- // 磁盘缓存失败,回退到内存
303332 cachedData = types .NewMemoryCachedData (cleanBase64 , mimeType , int64 (len (decodedData )))
304333 } else {
305334 cachedData = types .NewDiskCachedData (diskPath , mimeType , int64 (len (decodedData )))
335+ cachedData .DiskSize = base64Size
336+ cachedData .OnClose = func (size int64 ) {
337+ common .DecrementDiskFiles (size )
338+ }
306339 common .IncrementDiskFiles (base64Size )
307340 }
308341 } else {
309342 cachedData = types .NewMemoryCachedData (cleanBase64 , mimeType , int64 (len (decodedData )))
310343 }
311344
312- // 如果是图片或 MIME 类型未知,尝试解码图片获取更多信息
313345 if mimeType == "" || strings .HasPrefix (mimeType , "image/" ) {
314346 config , format , err := decodeImageConfig (decodedData )
315347 if err == nil {
@@ -324,8 +356,7 @@ func loadFromBase64(base64String string, providedMimeType string) (*types.Cached
324356 return cachedData , nil
325357}
326358
327- // GetImageConfig 获取图片配置(宽高等信息)
328- // 会自动处理缓存,避免重复下载/解码
359+ // GetImageConfig 获取图片配置
329360func GetImageConfig (c * gin.Context , source * types.FileSource ) (image.Config , string , error ) {
330361 cachedData , err := LoadFileSource (c , source , "get_image_config" )
331362 if err != nil {
@@ -336,7 +367,6 @@ func GetImageConfig(c *gin.Context, source *types.FileSource) (image.Config, str
336367 return * cachedData .ImageConfig , cachedData .ImageFormat , nil
337368 }
338369
339- // 如果缓存中没有图片配置,尝试解码
340370 base64Str , err := cachedData .GetBase64Data ()
341371 if err != nil {
342372 return image.Config {}, "" , fmt .Errorf ("failed to get base64 data: %w" , err )
@@ -351,16 +381,13 @@ func GetImageConfig(c *gin.Context, source *types.FileSource) (image.Config, str
351381 return image.Config {}, "" , err
352382 }
353383
354- // 更新缓存
355384 cachedData .ImageConfig = & config
356385 cachedData .ImageFormat = format
357386
358387 return config , format , nil
359388}
360389
361390// GetBase64Data 获取 base64 编码的数据
362- // 会自动处理缓存,避免重复下载
363- // 支持内存缓存和磁盘缓存
364391func GetBase64Data (c * gin.Context , source * types.FileSource , reason ... string ) (string , string , error ) {
365392 cachedData , err := LoadFileSource (c , source , reason ... )
366393 if err != nil {
@@ -375,28 +402,25 @@ func GetBase64Data(c *gin.Context, source *types.FileSource, reason ...string) (
375402
376403// GetMimeType 获取文件的 MIME 类型
377404func GetMimeType (c * gin.Context , source * types.FileSource ) (string , error ) {
378- // 如果已经有缓存,直接返回
379405 if source .HasCache () {
380406 return source .GetCache ().MimeType , nil
381407 }
382408
383- // 如果是 URL,尝试只获取 header 而不下载完整文件
384409 if source .IsURL () {
385410 mimeType , err := GetFileTypeFromUrl (c , source .URL , "get_mime_type" )
386411 if err == nil && mimeType != "" && mimeType != "application/octet-stream" {
387412 return mimeType , nil
388413 }
389414 }
390415
391- // 否则加载完整数据
392416 cachedData , err := LoadFileSource (c , source , "get_mime_type" )
393417 if err != nil {
394418 return "" , err
395419 }
396420 return cachedData .MimeType , nil
397421}
398422
399- // DetectFileType 检测文件类型(image/audio/video/file)
423+ // DetectFileType 检测文件类型
400424func DetectFileType (mimeType string ) types.FileType {
401425 if strings .HasPrefix (mimeType , "image/" ) {
402426 return types .FileTypeImage
@@ -414,13 +438,11 @@ func DetectFileType(mimeType string) types.FileType {
414438func decodeImageConfig (data []byte ) (image.Config , string , error ) {
415439 reader := bytes .NewReader (data )
416440
417- // 尝试标准格式
418441 config , format , err := image .DecodeConfig (reader )
419442 if err == nil {
420443 return config , format , nil
421444 }
422445
423- // 尝试 webp
424446 reader .Seek (0 , io .SeekStart )
425447 config , err = webp .DecodeConfig (reader )
426448 if err == nil {
@@ -432,13 +454,11 @@ func decodeImageConfig(data []byte) (image.Config, string, error) {
432454
433455// guessMimeTypeFromURL 从 URL 猜测 MIME 类型
434456func guessMimeTypeFromURL (url string ) string {
435- // 移除查询参数
436457 cleanedURL := url
437458 if q := strings .Index (cleanedURL , "?" ); q != - 1 {
438459 cleanedURL = cleanedURL [:q ]
439460 }
440461
441- // 获取最后一段
442462 if slash := strings .LastIndex (cleanedURL , "/" ); slash != - 1 && slash + 1 < len (cleanedURL ) {
443463 last := cleanedURL [slash + 1 :]
444464 if dot := strings .LastIndex (last , "." ); dot != - 1 && dot + 1 < len (last ) {
0 commit comments