@@ -149,29 +149,56 @@ public Task<string> GetPublicFileUrlAsync(string objectName)
149149 if ( string . IsNullOrWhiteSpace ( objectName ) )
150150 throw new ArgumentException ( "Invalid object name" , nameof ( objectName ) ) ;
151151
152- objectName = objectName . TrimStart ( '/' ) ;
153- var key = Uri . EscapeDataString ( objectName ) ;
154-
155- // Nếu có PublicEndpoint thì dùng nó làm base (có thể gồm scheme + path), KHÔNG thêm :port
152+ // 1) Chuẩn hoá key và chỉ encode từng segment, không encode dấu '/'
153+ var key = string . Join ( "/" ,
154+ objectName . Trim ( ) . TrimStart ( '/' )
155+ . Split ( '/' , StringSplitOptions . RemoveEmptyEntries )
156+ . Select ( s => Uri . EscapeDataString ( s ) )
157+ ) ;
158+
159+ // 2) Nếu có PublicEndpoint (vd: https://codecampus.site/s3) thì dùng làm base
156160 var pub = _config . PublicEndpoint ? . Trim ( ) . TrimEnd ( '/' ) ;
157161 if ( ! string . IsNullOrEmpty ( pub ) )
158162 {
159- // Nếu thiếu scheme thì tự thêm theo cấu hình Secure
160163 if ( ! pub . StartsWith ( "http://" , StringComparison . OrdinalIgnoreCase ) &&
161164 ! pub . StartsWith ( "https://" , StringComparison . OrdinalIgnoreCase ) )
162165 {
163166 pub = $ "{ ( _config . Secure ? "https" : "http" ) } ://{ pub } ";
164167 }
165- var url = $ "{ pub } /{ _config . BucketName } /{ key } ";
166- Console . WriteLine ( $ "Generated public URL: { url } ") ;
167- return Task . FromResult ( url ) ;
168+ return Task . FromResult ( $ "{ pub } /{ _config . BucketName } /{ key } ") ;
168169 }
169170
170- // Fallback: dùng endpoint nội bộ (có :port)
171+ // 3) Fallback: endpoint nội bộ MinIO
171172 var scheme = _config . Secure ? "https" : "http" ;
172- var direct = $ "{ scheme } ://{ _config . Endpoint } :{ _config . Port } /{ _config . BucketName } /{ key } ";
173- Console . WriteLine ( $ "Generated direct URL: { direct } ") ;
174- return Task . FromResult ( direct ) ;
173+ return Task . FromResult ( $ "{ scheme } ://{ _config . Endpoint } :{ _config . Port } /{ _config . BucketName } /{ key } ") ;
174+ }
175+
176+ // Thêm tiện ích: nhận vào URL hoặc key, trả về object key dùng cho MinIO
177+ public string UrlToObjectKey ( string urlOrKey )
178+ {
179+ if ( string . IsNullOrWhiteSpace ( urlOrKey ) ) return urlOrKey ;
180+
181+ var s = urlOrKey . Trim ( ) ;
182+
183+ // Nếu đã là key thì trả về key (bỏ dấu '/')
184+ if ( ! s . StartsWith ( "http://" , StringComparison . OrdinalIgnoreCase ) &&
185+ ! s . StartsWith ( "https://" , StringComparison . OrdinalIgnoreCase ) )
186+ return s . TrimStart ( '/' ) ;
187+
188+ // Cắt bỏ PublicEndpoint (nếu khớp)
189+ var pub = _config . PublicEndpoint ? . Trim ( ) . TrimEnd ( '/' ) ?? "" ;
190+ if ( ! string . IsNullOrEmpty ( pub ) && s . StartsWith ( pub , StringComparison . OrdinalIgnoreCase ) )
191+ s = s . Substring ( pub . Length ) ;
192+
193+ s = s . TrimStart ( '/' ) ;
194+
195+ // Cắt bỏ bucket name nếu có
196+ var bucketPrefix = _config . BucketName + "/" ;
197+ if ( s . StartsWith ( bucketPrefix , StringComparison . OrdinalIgnoreCase ) )
198+ s = s . Substring ( bucketPrefix . Length ) ;
199+
200+ // Giải mã phần còn lại (đề phòng URL cũ có %2F)
201+ return Uri . UnescapeDataString ( s ) ;
175202 }
176203
177204 public async Task < string > GeneratePresignedUrlAsync ( string objectName , int expirySeconds )
0 commit comments