* fix:修复响应污染问题 * fix:修改存储相关文件为中文注释 * fix:修复二次退款问题 * fix:修复多个考虑不全面问题 1.误删用户数据 2.R2存储默认区域 3.删除失败导致孤儿记录 4.网络抖动事变时重试 5.前端无限次请求获取图片 6.文件管理权限 * fix:修复字符串截断异常问题和文件下载超时问题 * fix:修复多图问题 * fix:异步生成图片时未配置oss提示错误信息 * fix:修复关闭异步后同步生成出错问题 * feat:新增文件存储测试功能,防止设置后不生效 * fix:修复同步图片转异步后计费错误 * fix:修复无下限扣款问题 * fix:修复结算重复扣款问题 * fix:修复计费出错导致的0费用结算 * fix:修复回调双重履约 * fix:修复预扣事务 * fix:修复费用字段类型问题 * fix:修复缓存问题 * fix:修复安全问题 * fix:修复数据库操作事务问题 * refactor:重构事务使用方式,改为官方推荐的dao形式 * fix:修复余额更新隐患 * fix:修复N+1查询问题 * fix:修复密钥安全问题 * fix:结算失败时打印错误日志 * improve:优化扣款 * fix:修复用户接口越权 --------- Co-authored-by: chengqian <wuchengqian@kkidc.com>
111 lines
3.0 KiB
Go
111 lines
3.0 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
|
)
|
|
|
|
// OSSStorageProvider 基于阿里云 OSS 实现 StorageProvider。
|
|
type OSSStorageProvider struct {
|
|
client *oss.Client
|
|
bucket *oss.Bucket
|
|
prefix string
|
|
}
|
|
|
|
// NewOSSProvider 创建一个阿里云 OSS 存储 provider。
|
|
func NewOSSProvider(cfg *StorageConfig) (*OSSStorageProvider, error) {
|
|
if cfg.Endpoint == "" {
|
|
return nil, fmt.Errorf("oss endpoint is required")
|
|
}
|
|
|
|
client, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.SecretKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create oss client: %w", err)
|
|
}
|
|
|
|
bucket, err := client.Bucket(cfg.Bucket)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get oss bucket: %w", err)
|
|
}
|
|
|
|
return &OSSStorageProvider{
|
|
client: client,
|
|
bucket: bucket,
|
|
prefix: cfg.PathPrefix,
|
|
}, nil
|
|
}
|
|
|
|
// fullKey 返回带前缀的完整对象 key(对已带前缀的存量 key 幂等)。
|
|
func (s *OSSStorageProvider) fullKey(key string) string {
|
|
return applyStoragePrefix(s.prefix, key)
|
|
}
|
|
|
|
// Upload 上传文件到阿里云 OSS 并返回存储 key。
|
|
func (s *OSSStorageProvider) Upload(ctx context.Context, reader io.Reader, key string, contentType string) (string, error) {
|
|
fullKey := s.fullKey(key)
|
|
|
|
options := []oss.Option{
|
|
oss.ContentType(contentType),
|
|
}
|
|
err := s.bucket.PutObject(fullKey, reader, options...)
|
|
if err != nil {
|
|
return "", fmt.Errorf("oss put object: %w", err)
|
|
}
|
|
|
|
return fullKey, nil
|
|
}
|
|
|
|
// Download 返回指定 key 文件的读取器。
|
|
func (s *OSSStorageProvider) Download(ctx context.Context, key string) (io.ReadCloser, error) {
|
|
fullKey := s.fullKey(key)
|
|
|
|
resp, err := s.bucket.GetObject(fullKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("oss get object: %w", err)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// Delete 从阿里云 OSS 删除文件。
|
|
func (s *OSSStorageProvider) Delete(ctx context.Context, key string) error {
|
|
fullKey := s.fullKey(key)
|
|
|
|
err := s.bucket.DeleteObject(fullKey)
|
|
if err != nil {
|
|
return fmt.Errorf("oss delete object: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PresignedURL 生成用于下载的临时签名 URL。
|
|
func (s *OSSStorageProvider) PresignedURL(ctx context.Context, key string, expires time.Duration) (string, error) {
|
|
fullKey := s.fullKey(key)
|
|
|
|
signedURL, err := s.bucket.SignURL(fullKey, oss.HTTPGet, int64(expires.Seconds()))
|
|
if err != nil {
|
|
return "", fmt.Errorf("oss sign url: %w", err)
|
|
}
|
|
|
|
return signedURL, nil
|
|
}
|
|
|
|
// PresignedThumbnailURL 在签名 URL 中附加 x-oss-process 的 image/resize 处理参数,
|
|
// 让 OSS 返回按比例缩小的缩略图(宽度按像素,高度自适应,不放大)。
|
|
func (s *OSSStorageProvider) PresignedThumbnailURL(ctx context.Context, key string, width int, expires time.Duration) (string, error) {
|
|
fullKey := s.fullKey(key)
|
|
|
|
process := fmt.Sprintf("image/resize,w_%d", width)
|
|
signedURL, err := s.bucket.SignURL(fullKey, oss.HTTPGet, int64(expires.Seconds()), oss.Process(process))
|
|
if err != nil {
|
|
return "", fmt.Errorf("oss sign thumbnail url: %w", err)
|
|
}
|
|
|
|
return signedURL, nil
|
|
}
|