@@ -30,8 +30,12 @@ import (
30
30
"path"
31
31
"path/filepath"
32
32
"strings"
33
+ "time"
33
34
35
+ "github.com/apache/answer/internal/entity"
36
+ "github.com/apache/answer/internal/repo/file"
34
37
"github.com/apache/answer/internal/service/file_record"
38
+ "github.com/google/uuid"
35
39
36
40
"github.com/apache/answer/internal/base/constant"
37
41
"github.com/apache/answer/internal/base/reason"
65
69
}
66
70
)
67
71
72
+ var (
73
+ FileStorageMode = os .Getenv ("FILE_STORAGE_MODE" ) // eg "fs" or "db"
74
+ )
75
+
68
76
type UploaderService interface {
69
77
UploadAvatarFile (ctx * gin.Context , userID string ) (url string , err error )
70
78
UploadPostFile (ctx * gin.Context , userID string ) (url string , err error )
@@ -78,13 +86,15 @@ type uploaderService struct {
78
86
serviceConfig * service_config.ServiceConfig
79
87
siteInfoService siteinfo_common.SiteInfoCommonService
80
88
fileRecordService * file_record.FileRecordService
89
+ fileRepo file.FileRepo
81
90
}
82
91
83
92
// NewUploaderService new upload service
84
93
func NewUploaderService (
85
94
serviceConfig * service_config.ServiceConfig ,
86
95
siteInfoService siteinfo_common.SiteInfoCommonService ,
87
96
fileRecordService * file_record.FileRecordService ,
97
+ fileRepo file.FileRepo ,
88
98
) UploaderService {
89
99
for _ , subPath := range subPathList {
90
100
err := dir .CreateDirIfNotExist (filepath .Join (serviceConfig .UploadPath , subPath ))
@@ -96,9 +106,14 @@ func NewUploaderService(
96
106
serviceConfig : serviceConfig ,
97
107
siteInfoService : siteInfoService ,
98
108
fileRecordService : fileRecordService ,
109
+ fileRepo : fileRepo ,
99
110
}
100
111
}
101
112
113
+ func UseDbStorage () bool {
114
+ return FileStorageMode != "fs"
115
+ }
116
+
102
117
// UploadAvatarFile upload avatar file
103
118
func (us * uploaderService ) UploadAvatarFile (ctx * gin.Context , userID string ) (url string , err error ) {
104
119
url , err = us .tryToUploadByPlugin (ctx , plugin .UserAvatar )
@@ -126,8 +141,8 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context, userID string) (ur
126
141
}
127
142
128
143
newFilename := fmt .Sprintf ("%s%s" , uid .IDStr12 (), fileExt )
129
- avatarFilePath := path . Join ( constant . AvatarSubPath , newFilename )
130
- return us .uploadImageFile (ctx , fileHeader , avatarFilePath )
144
+ fileHeader . Filename = newFilename
145
+ return us .uploadImageFile (ctx , fileHeader , constant . AvatarSubPath )
131
146
}
132
147
133
148
func (us * uploaderService ) AvatarThumbFile (ctx * gin.Context , fileName string , size int ) (url string , err error ) {
@@ -209,12 +224,14 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context, userID string) (
209
224
210
225
fileExt := strings .ToLower (path .Ext (fileHeader .Filename ))
211
226
newFilename := fmt .Sprintf ("%s%s" , uid .IDStr12 (), fileExt )
212
- avatarFilePath := path .Join (constant .PostSubPath , newFilename )
213
- url , err = us .uploadImageFile (ctx , fileHeader , avatarFilePath )
227
+ fileHeader .Filename = newFilename
228
+ url , err = us .uploadImageFile (ctx , fileHeader , constant .PostSubPath )
229
+ postFilePath := path .Join (constant .PostSubPath , newFilename )
230
+
214
231
if err != nil {
215
232
return "" , err
216
233
}
217
- us .fileRecordService .AddFileRecord (ctx , userID , avatarFilePath , url , string (plugin .UserPost ))
234
+ us .fileRecordService .AddFileRecord (ctx , userID , postFilePath , url , string (plugin .UserPost ))
218
235
return url , nil
219
236
}
220
237
@@ -279,10 +296,9 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context, userID string) (
279
296
if _ , ok := plugin.DefaultFileTypeCheckMapping [plugin.AdminBranding ][fileExt ]; ! ok {
280
297
return "" , errors .BadRequest (reason .RequestFormatError ).WithError (err )
281
298
}
282
-
283
299
newFilename := fmt .Sprintf ("%s%s" , uid .IDStr12 (), fileExt )
284
- avatarFilePath := path . Join ( constant . BrandingSubPath , newFilename )
285
- return us .uploadImageFile (ctx , fileHeader , avatarFilePath )
300
+ fileHeader . Filename = newFilename
301
+ return us .uploadImageFile (ctx , fileHeader , constant . BrandingSubPath )
286
302
}
287
303
288
304
func (us * uploaderService ) uploadImageFile (ctx * gin.Context , file * multipart.FileHeader , fileSubPath string ) (
@@ -295,7 +311,37 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil
295
311
if err != nil {
296
312
return "" , err
297
313
}
298
- filePath := path .Join (us .serviceConfig .UploadPath , fileSubPath )
314
+ if UseDbStorage () {
315
+ src , err := file .Open ()
316
+ if err != nil {
317
+ return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
318
+ }
319
+ defer src .Close ()
320
+
321
+ buffer := new (bytes.Buffer )
322
+ if _ , err = io .Copy (buffer , src ); err != nil {
323
+ return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
324
+ }
325
+
326
+ file := & entity.File {
327
+ ID : uuid .New ().String (),
328
+ FileName : file .Filename ,
329
+ MimeType : file .Header .Get ("Content-Type" ),
330
+ Size : int64 (len (buffer .Bytes ())),
331
+ Content : buffer .Bytes (),
332
+ CreatedAt : time .Now (),
333
+ }
334
+
335
+ err = us .fileRepo .Save (ctx , file )
336
+ if err != nil {
337
+ return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
338
+ }
339
+
340
+ return fmt .Sprintf ("%s/answer/api/v1/file/%s/%s" , siteGeneral .SiteUrl , fileSubPath , file .ID ), nil
341
+ //TODO checks: DecodeAndCheckImageFile removeExif
342
+ }
343
+ filePath := path .Join (us .serviceConfig .UploadPath , fileSubPath , file .Filename )
344
+
299
345
if err := ctx .SaveUploadedFile (file , filePath ); err != nil {
300
346
return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
301
347
}
@@ -324,6 +370,35 @@ func (us *uploaderService) uploadAttachmentFile(ctx *gin.Context, file *multipar
324
370
if err != nil {
325
371
return "" , err
326
372
}
373
+ if UseDbStorage () {
374
+ src , err := file .Open ()
375
+ if err != nil {
376
+ return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
377
+ }
378
+ defer src .Close ()
379
+
380
+ buf := new (bytes.Buffer )
381
+ if _ , err = io .Copy (buf , src ); err != nil {
382
+ return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
383
+ }
384
+
385
+ blob := & entity.File {
386
+ ID : uuid .New ().String (),
387
+ FileName : originalFilename ,
388
+ MimeType : file .Header .Get ("Content-Type" ),
389
+ Size : int64 (len (buf .Bytes ())),
390
+ Content : buf .Bytes (),
391
+ CreatedAt : time .Now (),
392
+ }
393
+
394
+ err = us .fileRepo .Save (ctx , blob )
395
+ if err != nil {
396
+ return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
397
+ }
398
+
399
+ downloadUrl = fmt .Sprintf ("%s/answer/api/v1/file/%s?download=%s" , siteGeneral .SiteUrl , blob .ID , url .QueryEscape (originalFilename ))
400
+ return downloadUrl , nil
401
+ }
327
402
filePath := path .Join (us .serviceConfig .UploadPath , fileSubPath )
328
403
if err := ctx .SaveUploadedFile (file , filePath ); err != nil {
329
404
return "" , errors .InternalServer (reason .UnknownError ).WithError (err ).WithStack ()
0 commit comments