Files
team-api/internal/logic/common/storage_oss.go
T
Qianandchengqian be9e707bfd 修复异步图片导致的多个问题 (#12)
* 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>
2026-07-22 17:53:18 +08:00

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
}