|
|
|
@ -111,7 +111,6 @@ func NewMinio() (s3.Interface, error) {
|
|
|
|
|
if err := m.initMinio(ctx); err != nil {
|
|
|
|
|
fmt.Println("init minio error:", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -142,9 +141,8 @@ func (m *Minio) initMinio(ctx context.Context) error {
|
|
|
|
|
return fmt.Errorf("check bucket exists error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if !exists {
|
|
|
|
|
err2 := m.core.Client.MakeBucket(ctx, conf.Bucket, minio.MakeBucketOptions{})
|
|
|
|
|
if err2 != nil {
|
|
|
|
|
return fmt.Errorf("make bucket error: %w", err2)
|
|
|
|
|
if err := m.core.Client.MakeBucket(ctx, conf.Bucket, minio.MakeBucketOptions{}); err != nil {
|
|
|
|
|
return fmt.Errorf("make bucket error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if conf.PublicRead {
|
|
|
|
@ -152,9 +150,8 @@ func (m *Minio) initMinio(ctx context.Context) error {
|
|
|
|
|
`{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject","s3:PutObject"],"Effect": "Allow","Principal": {"AWS": ["*"]},"Resource": ["arn:aws:s3:::%s/*"],"Sid": ""}]}`,
|
|
|
|
|
conf.Bucket,
|
|
|
|
|
)
|
|
|
|
|
err2 := m.core.Client.SetBucketPolicy(ctx, conf.Bucket, policy)
|
|
|
|
|
if err2 != nil {
|
|
|
|
|
return err2
|
|
|
|
|
if err := m.core.Client.SetBucketPolicy(ctx, conf.Bucket, policy); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m.location, err = m.core.Client.GetBucketLocation(ctx, conf.Bucket)
|
|
|
|
@ -194,6 +191,7 @@ func (m *Minio) Engine() string {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) PartLimit() *s3.PartLimit {
|
|
|
|
|
|
|
|
|
|
return &s3.PartLimit{
|
|
|
|
|
MinPartSize: minPartSize,
|
|
|
|
|
MaxPartSize: maxPartSize,
|
|
|
|
@ -358,16 +356,14 @@ func (m *Minio) IsNotFound(err error) bool {
|
|
|
|
|
if err == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
var minioErr minio.ErrorResponse
|
|
|
|
|
if errors.As(err, &minio.ErrorResponse{}) {
|
|
|
|
|
return minioErr.StatusCode == http.StatusNotFound || minioErr.Code == "NoSuchKey"
|
|
|
|
|
}
|
|
|
|
|
var minioErr2 *minio.ErrorResponse
|
|
|
|
|
if errors.As(err, &minioErr2) {
|
|
|
|
|
return minioErr2.StatusCode == http.StatusNotFound || minioErr2.Code == "NoSuchKey"
|
|
|
|
|
switch e := err.(type) {
|
|
|
|
|
case minio.ErrorResponse:
|
|
|
|
|
return e.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey"
|
|
|
|
|
case *minio.ErrorResponse:
|
|
|
|
|
return e.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey"
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) AbortMultipartUpload(ctx context.Context, uploadID string, name string) error {
|
|
|
|
@ -430,13 +426,11 @@ func (m *Minio) presignedGetObject(ctx context.Context, name string, expire time
|
|
|
|
|
return rawURL.String(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) getImageInfoForAccessURL(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
name string,
|
|
|
|
|
expire time.Duration,
|
|
|
|
|
opt *s3.AccessURLOption,
|
|
|
|
|
reqParams url.Values,
|
|
|
|
|
) (fileInfo *s3.ObjectInfo, objectInfoPath, msg string, err error) {
|
|
|
|
|
func (m *Minio) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) {
|
|
|
|
|
if err := m.initMinio(ctx); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
reqParams := make(url.Values)
|
|
|
|
|
if opt != nil {
|
|
|
|
|
if opt.ContentType != "" {
|
|
|
|
|
reqParams.Set("response-content-type", opt.ContentType)
|
|
|
|
@ -446,47 +440,35 @@ func (m *Minio) getImageInfoForAccessURL(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if opt.Image == nil || (opt.Image.Width < 0 && opt.Image.Height < 0 && opt.Image.Format == "") || (opt.Image.Width > maxImageWidth || opt.Image.Height > maxImageHeight) {
|
|
|
|
|
msg, err = m.presignedGetObject(ctx, name, expire, reqParams)
|
|
|
|
|
|
|
|
|
|
return nil, "", msg, err
|
|
|
|
|
return m.presignedGetObject(ctx, name, expire, reqParams)
|
|
|
|
|
}
|
|
|
|
|
fileInfo, err = m.StatObject(ctx, name)
|
|
|
|
|
objectInfoPath = path.Join(pathInfo, fileInfo.ETag, "image.json")
|
|
|
|
|
fileInfo, err := m.StatObject(ctx, name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, "", msg, err
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if fileInfo.Size > maxImageSize {
|
|
|
|
|
return nil, "", "", errors.New("file size too large")
|
|
|
|
|
return "", errors.New("file size too large")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fileInfo, objectInfoPath, "", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) loadImgDataForAccessURL(objectInfoPath string, ctx context.Context, name string, info *minioImageInfo) (img image.Image, msg string, err error) {
|
|
|
|
|
var data []byte
|
|
|
|
|
data, err = m.getObjectData(ctx, objectInfoPath, 1024)
|
|
|
|
|
|
|
|
|
|
//nolint:nestif //easy enough to understand
|
|
|
|
|
objectInfoPath := path.Join(pathInfo, fileInfo.ETag, "image.json")
|
|
|
|
|
var (
|
|
|
|
|
img image.Image
|
|
|
|
|
info minioImageInfo
|
|
|
|
|
)
|
|
|
|
|
data, err := m.getObjectData(ctx, objectInfoPath, 1024)
|
|
|
|
|
if err == nil {
|
|
|
|
|
err = json.Unmarshal(data, &info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, "", fmt.Errorf("unmarshal minio image info.json error: %w", err)
|
|
|
|
|
if err := json.Unmarshal(data, &info); err != nil {
|
|
|
|
|
return "", fmt.Errorf("unmarshal minio image info.json error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if info.NotImage {
|
|
|
|
|
return nil, "", errors.New("not image")
|
|
|
|
|
return "", errors.New("not image")
|
|
|
|
|
}
|
|
|
|
|
} else if m.IsNotFound(err) {
|
|
|
|
|
var reader *minio.Object
|
|
|
|
|
reader, err = m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{})
|
|
|
|
|
reader, err := m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return img, msg, err
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
defer reader.Close()
|
|
|
|
|
var (
|
|
|
|
|
imageInfo image.Image
|
|
|
|
|
format string
|
|
|
|
|
)
|
|
|
|
|
imageInfo, format, err = ImageStat(reader)
|
|
|
|
|
imageInfo, format, err := ImageStat(reader)
|
|
|
|
|
if err == nil {
|
|
|
|
|
info.NotImage = false
|
|
|
|
|
info.Format = format
|
|
|
|
@ -495,22 +477,16 @@ func (m *Minio) loadImgDataForAccessURL(objectInfoPath string, ctx context.Conte
|
|
|
|
|
} else {
|
|
|
|
|
info.NotImage = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data, err = json.Marshal(&info)
|
|
|
|
|
data, err := json.Marshal(&info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return img, msg, err
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = m.core.Client.PutObject(ctx, m.bucket, objectInfoPath, bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return img, msg, err
|
|
|
|
|
if _, err := m.core.Client.PutObject(ctx, m.bucket, objectInfoPath, bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{}); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return img, msg, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) formatImgInfoForAccessURL(opt *s3.AccessURLOption, info *minioImageInfo, reqParams url.Values) {
|
|
|
|
|
if opt.Image.Width > info.Width || opt.Image.Width <= 0 {
|
|
|
|
|
opt.Image.Width = info.Width
|
|
|
|
|
}
|
|
|
|
@ -533,24 +509,24 @@ func (m *Minio) formatImgInfoForAccessURL(opt *s3.AccessURLOption, info *minioIm
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
reqParams.Set("response-content-type", "image/"+opt.Image.Format)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) cacheImgInfoForAccessURL(ctx context.Context, name, cacheKey string, img image.Image, expire time.Duration, opt *s3.AccessURLOption, reqParams url.Values) (string, error) {
|
|
|
|
|
_, err := m.core.Client.StatObject(ctx, m.bucket, cacheKey, minio.StatObjectOptions{})
|
|
|
|
|
if err == nil {
|
|
|
|
|
if opt.Image.Width == info.Width && opt.Image.Height == info.Height && opt.Image.Format == info.Format {
|
|
|
|
|
return m.presignedGetObject(ctx, name, expire, reqParams)
|
|
|
|
|
}
|
|
|
|
|
cacheKey := filepath.Join(pathInfo, fileInfo.ETag, fmt.Sprintf("image_w%d_h%d.%s", opt.Image.Width, opt.Image.Height, opt.Image.Format))
|
|
|
|
|
if _, err := m.core.Client.StatObject(ctx, m.bucket, cacheKey, minio.StatObjectOptions{}); err == nil {
|
|
|
|
|
return m.presignedGetObject(ctx, cacheKey, expire, reqParams)
|
|
|
|
|
} else if !m.IsNotFound(err) {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if img == nil {
|
|
|
|
|
reader, err2 := m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{})
|
|
|
|
|
if err2 != nil {
|
|
|
|
|
return "", err2
|
|
|
|
|
reader, err := m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
defer reader.Close()
|
|
|
|
|
img, _, err2 = ImageStat(reader)
|
|
|
|
|
if err2 != nil {
|
|
|
|
|
return "", err2
|
|
|
|
|
img, _, err = ImageStat(reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
thumbnail := resizeImage(img, opt.Image.Width, opt.Image.Height)
|
|
|
|
@ -563,48 +539,10 @@ func (m *Minio) cacheImgInfoForAccessURL(ctx context.Context, name, cacheKey str
|
|
|
|
|
case formatGif:
|
|
|
|
|
err = gif.Encode(buf, thumbnail, nil)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if _, err := m.core.Client.PutObject(ctx, m.bucket, cacheKey, buf, int64(buf.Len()), minio.PutObjectOptions{}); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *Minio) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) {
|
|
|
|
|
errInit := m.initMinio(ctx)
|
|
|
|
|
if errInit != nil {
|
|
|
|
|
return "", errInit
|
|
|
|
|
}
|
|
|
|
|
reqParams := make(url.Values)
|
|
|
|
|
fileInfo, objectInfoPath, msg, err := m.getImageInfoForAccessURL(ctx, name, expire, opt, reqParams)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return msg, err
|
|
|
|
|
}
|
|
|
|
|
// load-cache img data
|
|
|
|
|
var (
|
|
|
|
|
img image.Image
|
|
|
|
|
info minioImageInfo
|
|
|
|
|
)
|
|
|
|
|
img, msg, err = m.loadImgDataForAccessURL(objectInfoPath, ctx, name, &info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return msg, err
|
|
|
|
|
}
|
|
|
|
|
// format img info
|
|
|
|
|
m.formatImgInfoForAccessURL(opt, &info, reqParams)
|
|
|
|
|
// no need resize
|
|
|
|
|
if opt.Image.Width == info.Width && opt.Image.Height == info.Height && opt.Image.Format == info.Format {
|
|
|
|
|
return m.presignedGetObject(ctx, name, expire, reqParams)
|
|
|
|
|
}
|
|
|
|
|
// cache img
|
|
|
|
|
cacheKey := filepath.Join(pathInfo, fileInfo.ETag, fmt.Sprintf("image_w%d_h%d.%s", opt.Image.Width, opt.Image.Height, opt.Image.Format))
|
|
|
|
|
msg, err = m.cacheImgInfoForAccessURL(ctx, name, cacheKey, img, expire, opt, reqParams)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return msg, err
|
|
|
|
|
}
|
|
|
|
|
// return cache img
|
|
|
|
|
return m.presignedGetObject(ctx, cacheKey, expire, reqParams)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|