feat: support local storage (#1383)

* feat: support local storage

* update

* update

* update

* update
This commit is contained in:
Zeng1998
2023-03-19 19:37:57 +08:00
committed by GitHub
parent a21ff5c2e3
commit f3090b115d
10 changed files with 224 additions and 48 deletions

View File

@ -7,7 +7,9 @@ import (
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
@ -105,13 +107,13 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
}
defer src.Close()
systemSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{Name: api.SystemSettingStorageServiceIDName})
systemSettingStorageServiceID, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{Name: api.SystemSettingStorageServiceIDName})
if err != nil && common.ErrorCode(err) != common.NotFound {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
}
storageServiceID := 0
if systemSetting != nil {
err = json.Unmarshal([]byte(systemSetting.Value), &storageServiceID)
if systemSettingStorageServiceID != nil {
err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
}
@ -119,6 +121,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
var resourceCreate *api.ResourceCreate
if storageServiceID == 0 {
// Database storage.
fileBytes, err := io.ReadAll(src)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read file").SetInternal(err)
@ -130,6 +133,47 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
Size: size,
Blob: fileBytes,
}
} else if storageServiceID == -1 {
// Local storage.
systemSettingLocalStoragePath, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{Name: api.SystemSettingLocalStoragePathName})
if err != nil && common.ErrorCode(err) != common.NotFound {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
}
localStoragePath := ""
if systemSettingLocalStoragePath != nil {
err = json.Unmarshal([]byte(systemSettingLocalStoragePath.Value), &localStoragePath)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
}
}
filePath := localStoragePath
if !strings.Contains(filePath, "{filename}") {
filePath = path.Join(filePath, "{filename}")
}
filePath = path.Join(s.Profile.Data, replacePathTemplate(filePath, filename))
dirPath := filepath.Dir(filePath)
err = os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create directory").SetInternal(err)
}
dst, err := os.Create(filePath)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create file").SetInternal(err)
}
defer dst.Close()
_, err = io.Copy(dst, src)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to copy file").SetInternal(err)
}
resourceCreate = &api.ResourceCreate{
CreatorID: userID,
Filename: filename,
Type: filetype,
Size: size,
InternalPath: filePath,
}
} else {
storage, err := s.Store.FindStorage(ctx, &api.StorageFind{ID: &storageServiceID})
if err != nil {
@ -138,38 +182,11 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
if storage.Type == api.StorageS3 {
s3Config := storage.Config.S3Config
t := time.Now()
var s3FileKey string
if s3Config.Path == "" {
s3FileKey = filename
} else {
s3FileKey = fileKeyPattern.ReplaceAllStringFunc(s3Config.Path, func(s string) string {
switch s {
case "{filename}":
return filename
case "{timestamp}":
return fmt.Sprintf("%d", t.Unix())
case "{year}":
return fmt.Sprintf("%d", t.Year())
case "{month}":
return fmt.Sprintf("%02d", t.Month())
case "{day}":
return fmt.Sprintf("%02d", t.Day())
case "{hour}":
return fmt.Sprintf("%02d", t.Hour())
case "{minute}":
return fmt.Sprintf("%02d", t.Minute())
case "{second}":
return fmt.Sprintf("%02d", t.Second())
}
return s
})
if !strings.Contains(s3Config.Path, "{filename}") {
s3FileKey = path.Join(s3FileKey, filename)
}
if !strings.Contains(s3Config.Path, "{filename}") {
s3FileKey = path.Join(s3Config.Path, "{filename}")
}
s3FileKey = replacePathTemplate(s3FileKey, filename)
s3client, err := s3.NewClient(ctx, &s3.Config{
AccessKey: s3Config.AccessKey,
SecretKey: s3Config.SecretKey,
@ -387,16 +404,29 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find resource by ID: %v", resourceID)).SetInternal(err)
}
blob := resource.Blob
if resource.InternalPath != "" {
src, err := os.Open(resource.InternalPath)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to open the local resource: %s", resource.InternalPath)).SetInternal(err)
}
defer src.Close()
blob, err = io.ReadAll(src)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to read the local resource: %s", resource.InternalPath)).SetInternal(err)
}
}
c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable")
c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'self'")
resourceType := strings.ToLower(resource.Type)
if strings.HasPrefix(resourceType, "text") {
resourceType = echo.MIMETextPlainCharsetUTF8
} else if strings.HasPrefix(resourceType, "video") || strings.HasPrefix(resourceType, "audio") {
http.ServeContent(c.Response(), c.Request(), resource.Filename, time.Unix(resource.UpdatedTs, 0), bytes.NewReader(resource.Blob))
http.ServeContent(c.Response(), c.Request(), resource.Filename, time.Unix(resource.UpdatedTs, 0), bytes.NewReader(blob))
return nil
}
return c.Stream(http.StatusOK, resourceType, bytes.NewReader(resource.Blob))
return c.Stream(http.StatusOK, resourceType, bytes.NewReader(blob))
})
}
@ -422,3 +452,29 @@ func (s *Server) createResourceCreateActivity(c echo.Context, resource *api.Reso
}
return err
}
func replacePathTemplate(path string, filename string) string {
t := time.Now()
path = fileKeyPattern.ReplaceAllStringFunc(path, func(s string) string {
switch s {
case "{filename}":
return filename
case "{timestamp}":
return fmt.Sprintf("%d", t.Unix())
case "{year}":
return fmt.Sprintf("%d", t.Year())
case "{month}":
return fmt.Sprintf("%02d", t.Month())
case "{day}":
return fmt.Sprintf("%02d", t.Day())
case "{hour}":
return fmt.Sprintf("%02d", t.Hour())
case "{minute}":
return fmt.Sprintf("%02d", t.Minute())
case "{second}":
return fmt.Sprintf("%02d", t.Second())
}
return s
})
return path
}