feat(media): add MinIO storage switch and HTTPS OSS endpoint
This commit is contained in:
+102
-40
@@ -27,29 +27,33 @@ const (
|
||||
defaultServeURL = "/v0/file/s/"
|
||||
defaultCacheControl = "no-cache, must-revalidate"
|
||||
|
||||
handlerName = "oss"
|
||||
handlerName = "oss"
|
||||
minioHandlerName = "minio"
|
||||
// Presign GET URLs for this number of seconds.
|
||||
defaultPresignDuration = 120
|
||||
)
|
||||
|
||||
type ossConfig struct {
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
AccessKeySecret string `json:"access_key_secret"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
BucketName string `json:"bucket"`
|
||||
RegionID string `json:"region_id"`
|
||||
CorsOrigins []string `json:"cors_origins"`
|
||||
ServeURL string `json:"serve_url"`
|
||||
PresignTTL int `json:"presign_ttl"`
|
||||
CacheControl string `json:"cache_control"`
|
||||
VerifyBucket bool `json:"verify_bucket"`
|
||||
UseSysConfig bool `json:"use_sys_config"`
|
||||
Provider string `json:"provider"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
AccessKeySecret string `json:"access_key_secret"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
BucketName string `json:"bucket"`
|
||||
RegionID string `json:"region_id"`
|
||||
CorsOrigins []string `json:"cors_origins"`
|
||||
ServeURL string `json:"serve_url"`
|
||||
PresignTTL int `json:"presign_ttl"`
|
||||
CacheControl string `json:"cache_control"`
|
||||
VerifyBucket bool `json:"verify_bucket"`
|
||||
UseSysConfig bool `json:"use_sys_config"`
|
||||
MinIO minioConfig `json:"minio"`
|
||||
}
|
||||
|
||||
type ossHandler struct {
|
||||
client *aliyunoss.Client
|
||||
bucket *aliyunoss.Bucket
|
||||
conf ossConfig
|
||||
minio *minioStorage
|
||||
corsOrigins []media.AllowedOrigin
|
||||
}
|
||||
|
||||
@@ -138,21 +142,18 @@ func (oh *ossHandler) Init(jsconf string) error {
|
||||
if err = json.Unmarshal([]byte(jsconf), &oh.conf); err != nil {
|
||||
return errors.New("failed to parse config: " + err.Error())
|
||||
}
|
||||
if oh.conf.Provider == "" {
|
||||
oh.conf.Provider = handlerName
|
||||
}
|
||||
if oh.conf.Provider != handlerName && oh.conf.Provider != minioHandlerName {
|
||||
return errors.New("unsupported storage provider: " + oh.conf.Provider)
|
||||
}
|
||||
if err = oh.loadSysConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if oh.conf.AccessKeyID == "" {
|
||||
return errors.New("missing Access Key ID")
|
||||
}
|
||||
if oh.conf.AccessKeySecret == "" {
|
||||
return errors.New("missing Access Key Secret")
|
||||
}
|
||||
if oh.conf.Endpoint == "" {
|
||||
return errors.New("missing Endpoint")
|
||||
}
|
||||
if oh.conf.BucketName == "" {
|
||||
return errors.New("missing Bucket")
|
||||
if oh.conf.Provider == handlerName && (oh.conf.AccessKeyID == "" || oh.conf.AccessKeySecret == "" || oh.conf.Endpoint == "" || oh.conf.BucketName == "") {
|
||||
return errors.New("incomplete OSS configuration")
|
||||
}
|
||||
if oh.conf.PresignTTL <= 0 {
|
||||
oh.conf.PresignTTL = defaultPresignDuration
|
||||
@@ -169,21 +170,46 @@ func (oh *ossHandler) Init(jsconf string) error {
|
||||
return errors.New("failed to parse CORS allowed origins: " + err.Error())
|
||||
}
|
||||
|
||||
oh.client, err = aliyunoss.New(oh.conf.Endpoint, oh.conf.AccessKeyID, oh.conf.AccessKeySecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oh.conf.VerifyBucket {
|
||||
exists, err := oh.client.IsBucketExist(oh.conf.BucketName)
|
||||
if oh.conf.AccessKeyID != "" && oh.conf.AccessKeySecret != "" && oh.conf.Endpoint != "" && oh.conf.BucketName != "" {
|
||||
oh.conf.Endpoint = normalizeOSSEndpoint(oh.conf.Endpoint)
|
||||
oh.client, err = aliyunoss.New(oh.conf.Endpoint, oh.conf.AccessKeyID, oh.conf.AccessKeySecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return errors.New("bucket does not exist")
|
||||
if oh.conf.VerifyBucket && oh.conf.Provider == handlerName {
|
||||
exists, err := oh.client.IsBucketExist(oh.conf.BucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return errors.New("bucket does not exist")
|
||||
}
|
||||
}
|
||||
oh.bucket, err = oh.client.Bucket(oh.conf.BucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
oh.bucket, err = oh.client.Bucket(oh.conf.BucketName)
|
||||
return err
|
||||
oh.minio, err = newMinIOStorage(oh.conf.MinIO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oh.conf.Provider == minioHandlerName && oh.minio == nil {
|
||||
return errors.New("incomplete MinIO configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeOSSEndpoint(endpoint string) string {
|
||||
endpoint = strings.TrimSpace(endpoint)
|
||||
if !strings.Contains(endpoint, "://") {
|
||||
return "https://" + endpoint
|
||||
}
|
||||
parsed, err := url.Parse(endpoint)
|
||||
if err == nil && parsed.Scheme == "http" && strings.HasSuffix(strings.ToLower(parsed.Hostname()), ".aliyuncs.com") {
|
||||
return "https://" + strings.TrimPrefix(endpoint, "http://")
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
func (oh *ossHandler) loadSysConfig() error {
|
||||
@@ -191,6 +217,11 @@ func (oh *ossHandler) loadSysConfig() error {
|
||||
return nil
|
||||
}
|
||||
values, err := store.ThirdPartyConfig.Get("im_third_party",
|
||||
"minio_endpoint",
|
||||
"minio_bucket",
|
||||
"minio_region",
|
||||
"minio_access_key_id",
|
||||
"minio_secret_access_key",
|
||||
"oss_enabled",
|
||||
"oss_provider",
|
||||
"oss_endpoint",
|
||||
@@ -207,12 +238,17 @@ func (oh *ossHandler) loadSysConfig() error {
|
||||
if err != nil {
|
||||
return errors.New("failed to load OSS config from sys config: " + err.Error())
|
||||
}
|
||||
if !truthy(values["oss_enabled"]) {
|
||||
if oh.conf.Provider == handlerName && !truthy(values["oss_enabled"]) {
|
||||
return errors.New("OSS is disabled in sys config")
|
||||
}
|
||||
if provider := strings.TrimSpace(values["oss_provider"]); provider != "" && provider != handlerName {
|
||||
if provider := strings.TrimSpace(values["oss_provider"]); oh.conf.Provider == handlerName && provider != "" && provider != handlerName {
|
||||
return errors.New("unsupported storage provider in sys config: " + provider)
|
||||
}
|
||||
applyString(&oh.conf.MinIO.Endpoint, values["minio_endpoint"])
|
||||
applyString(&oh.conf.MinIO.Bucket, values["minio_bucket"])
|
||||
applyString(&oh.conf.MinIO.Region, values["minio_region"])
|
||||
applyString(&oh.conf.MinIO.AccessKeyID, values["minio_access_key_id"])
|
||||
applyString(&oh.conf.MinIO.SecretAccessKey, values["minio_secret_access_key"])
|
||||
applyString(&oh.conf.Endpoint, values["oss_endpoint"])
|
||||
applyString(&oh.conf.BucketName, values["oss_bucket"])
|
||||
applyString(&oh.conf.RegionID, values["oss_region_id"])
|
||||
@@ -308,6 +344,9 @@ func (oh *ossHandler) Headers(tenantID types.TenantID, method string, url *url.U
|
||||
|
||||
// Upload processes request for a file upload.
|
||||
func (oh *ossHandler) Upload(fdef *types.FileDef, file io.Reader) (string, int64, error) {
|
||||
if oh.conf.Provider == minioHandlerName {
|
||||
return oh.uploadMinIO(fdef, file)
|
||||
}
|
||||
key, err := tenantObjectKey(fdef.TenantID, fdef.Uid().String32(), fdef.CreatedAt)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
@@ -342,6 +381,9 @@ func (oh *ossHandler) Upload(fdef *types.FileDef, file io.Reader) (string, int64
|
||||
}
|
||||
|
||||
func (oh *ossHandler) UploadPublic(fdef *media.PublicFileDef, file io.Reader) (string, int64, error) {
|
||||
if oh.conf.Provider == minioHandlerName {
|
||||
return oh.uploadPublicMinIO(fdef, file)
|
||||
}
|
||||
key, err := publicObjectKey(fdef.Purpose, fdef.ID, fdef.CreatedAt)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
@@ -378,6 +420,12 @@ func (oh *ossHandler) Download(tenantID types.TenantID, url string) (*types.File
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if strings.HasPrefix(fd.Location, minioLocationPrefix) {
|
||||
return oh.downloadMinIO(fd)
|
||||
}
|
||||
if oh.bucket == nil {
|
||||
return nil, nil, errors.New("OSS is not configured for legacy file download")
|
||||
}
|
||||
objectKey, err := oh.resolveObjectKey(fd.Location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -405,12 +453,25 @@ func (oh *ossHandler) Download(tenantID types.TenantID, url string) (*types.File
|
||||
|
||||
// Delete deletes files from OSS by provided object keys.
|
||||
func (oh *ossHandler) Delete(locations []string) error {
|
||||
for i := 0; i < len(locations); i += 1000 {
|
||||
end := i + 1000
|
||||
if end > len(locations) {
|
||||
end = len(locations)
|
||||
ossLocations := make([]string, 0, len(locations))
|
||||
for _, location := range locations {
|
||||
if strings.HasPrefix(location, minioLocationPrefix) {
|
||||
if err := oh.deleteMinIO(location); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := oh.bucket.DeleteObjects(locations[i:end], aliyunoss.DeleteObjectsQuiet(true)); err != nil {
|
||||
ossLocations = append(ossLocations, location)
|
||||
}
|
||||
if len(ossLocations) > 0 && oh.bucket == nil {
|
||||
return errors.New("OSS is not configured for legacy file deletion")
|
||||
}
|
||||
for i := 0; i < len(ossLocations); i += 1000 {
|
||||
end := i + 1000
|
||||
if end > len(ossLocations) {
|
||||
end = len(ossLocations)
|
||||
}
|
||||
if _, err := oh.bucket.DeleteObjects(ossLocations[i:end], aliyunoss.DeleteObjectsQuiet(true)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -504,4 +565,5 @@ func etagFromLocation(location string) string {
|
||||
|
||||
func init() {
|
||||
store.RegisterMediaHandler(handlerName, &ossHandler{})
|
||||
store.RegisterMediaHandler(minioHandlerName, &ossHandler{})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user