improve image sharing function

This commit is contained in:
2025-02-20 23:04:58 +08:00
parent b196e50aee
commit db4c59f6f6
37 changed files with 2188 additions and 95 deletions

View File

@@ -0,0 +1,67 @@
package share
import (
"context"
"errors"
"time"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ListShareRecordLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListShareRecordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListShareRecordLogic {
return &ListShareRecordLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListShareRecordLogic) ListShareRecord(req *types.ShareRecordListRequest) (resp *types.ShareRecordListResponse, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return nil, errors.New("user_id not found")
}
storageShare := l.svcCtx.DB.ScaStorageShare
storageAlbum := l.svcCtx.DB.ScaStorageAlbum
var recordList []types.ShareRecord
query := storageShare.
Select(storageShare.ID,
storageShare.ShareCode,
storageShare.VisitLimit,
storageShare.AccessPassword,
storageShare.ValidityPeriod,
storageShare.CreatedAt,
storageAlbum.CoverImage).
LeftJoin(storageAlbum, storageShare.AlbumID.EqCol(storageAlbum.ID)).
Where(storageShare.UserID.Eq(uid)).
Order(storageShare.CreatedAt.Desc())
if len(req.DateRange) == 2 {
startDate, errStart := time.Parse("2006-01-02", req.DateRange[0])
endDate, errEnd := time.Parse("2006-01-02", req.DateRange[1])
if errStart != nil || errEnd != nil {
return nil, errors.New("invalid date format")
}
// Ensure endDate is inclusive by adding 24 hours
endDate = endDate.AddDate(0, 0, 1)
query = query.Where(storageShare.CreatedAt.Between(startDate, endDate))
}
err = query.Scan(&recordList)
if err != nil {
return nil, err
}
resp = &types.ShareRecordListResponse{
Records: recordList,
}
return resp, nil
}

View File

@@ -0,0 +1,316 @@
package share
import (
"context"
"encoding/json"
"errors"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
"gorm.io/gorm"
"net/url"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/encrypt"
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
"time"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type QueryShareImageLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewQueryShareImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryShareImageLogic {
return &QueryShareImageLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryShareImageLogic) QueryShareImage(req *types.QueryShareImageRequest) (resp *types.QueryShareImageResponse, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return nil, errors.New("user_id not found")
}
// 获取分享记录
cacheKey := constant.ImageSharePrefix + req.ShareCode
shareData, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, errors.New("share code not found")
}
return nil, err
}
var storageShare model.ScaStorageShare
if err := json.Unmarshal([]byte(shareData), &storageShare); err != nil {
return nil, errors.New("unmarshal share data failed")
}
// 验证密码
if storageShare.AccessPassword != "" && storageShare.AccessPassword != req.AccessPassword {
return nil, errors.New("incorrect password")
}
// 检查分享是否过期
if storageShare.ExpireTime.Before(time.Now()) {
return nil, errors.New("share link has expired")
}
// 检查访问限制
if storageShare.VisitLimit > 0 {
err = l.incrementVisitCount(req.ShareCode, storageShare.VisitLimit)
if err != nil {
return nil, err
}
}
// 记录用户访问
err = l.recordUserVisit(storageShare.ID, uid)
if err != nil {
logx.Error("Failed to record user visit:", err)
return nil, err
}
// 生成缓存键(在验证通过后)
resultCacheKey := constant.ImageListPrefix + req.ShareCode + ":" + req.AccessPassword
// 尝试从缓存中获取结果
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, resultCacheKey).Result()
if err == nil {
// 缓存命中,直接返回缓存结果
var cachedResponse types.QueryShareImageResponse
if err := json.Unmarshal([]byte(cachedResult), &cachedResponse); err == nil {
return &cachedResponse, nil
}
logx.Error("Failed to unmarshal cached result:", err)
} else if !errors.Is(err, redis.Nil) {
// 如果 Redis 查询出错(非缓存未命中),记录错误并继续回源查询
logx.Error("Failed to get cached result from Redis:", err)
}
// 缓存未命中,执行回源查询逻辑
resp, err = l.queryShareImageFromSource(&storageShare)
if err != nil {
return nil, err
}
// 将查询结果缓存到 Redis
respBytes, err := json.Marshal(resp)
if err != nil {
logx.Error("Failed to marshal response for caching:", err)
} else {
// 设置缓存,过期时间为 5 分钟
err = l.svcCtx.RedisClient.Set(l.ctx, resultCacheKey, respBytes, 5*time.Minute).Err()
if err != nil {
logx.Error("Failed to cache result in Redis:", err)
}
}
return resp, nil
}
func (l *QueryShareImageLogic) queryShareImageFromSource(storageShare *model.ScaStorageShare) (resp *types.QueryShareImageResponse, err error) {
// 查询相册图片列表
storageInfo := l.svcCtx.DB.ScaStorageInfo
storageThumb := l.svcCtx.DB.ScaStorageThumb
var storageInfoList []types.ShareFileInfoResult
err = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageInfo.Provider,
storageInfo.Bucket,
storageInfo.Path,
storageThumb.ThumbPath,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
Where(
storageInfo.Type.Eq(constant.ImageTypeShared),
storageInfo.AlbumID.Eq(storageShare.AlbumID)).
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
if err != nil {
return nil, err
}
// 使用 errgroup 和 semaphore 并发处理图片信息
var ResultList []types.ShareImageListMeta
g, ctx := errgroup.WithContext(l.ctx)
sem := semaphore.NewWeighted(10) // 限制并发数为 10
for _, imgInfo := range storageInfoList {
imgInfo := imgInfo // 创建局部变量,避免闭包问题
if err := sem.Acquire(ctx, 1); err != nil {
return nil, err
}
g.Go(func() error {
defer sem.Release(1)
// 加载用户oss配置信息
cacheOssConfigKey := constant.UserOssConfigPrefix + storageShare.UserID + ":" + imgInfo.Provider
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheOssConfigKey, storageShare.UserID, imgInfo.Provider)
if err != nil {
return err
}
service, err := l.svcCtx.StorageManager.GetStorage(storageShare.UserID, ossConfig)
if err != nil {
return errors.New("get storage failed")
}
ossURL, err := service.PresignedURL(ctx, ossConfig.BucketName, imgInfo.Path, 30*time.Minute)
if err != nil {
return errors.New("get presigned url failed")
}
reqParams := make(url.Values)
presignedURL, err := l.svcCtx.MinioClient.PresignedGetObject(ctx, constant.ThumbnailBucketName, imgInfo.ThumbPath, 30*time.Minute, reqParams)
if err != nil {
return errors.New("get presigned thumbnail url failed")
}
ResultList = append(ResultList, types.ShareImageListMeta{
ID: imgInfo.ID,
FileName: imgInfo.FileName,
ThumbH: imgInfo.ThumbH,
ThumbW: imgInfo.ThumbW,
ThumbSize: imgInfo.ThumbSize,
CreatedAt: imgInfo.CreatedAt.Format(constant.TimeFormat),
URL: ossURL,
Thumbnail: presignedURL.String(),
})
return nil
})
}
// 等待所有并发任务完成
if err := g.Wait(); err != nil {
return nil, err
}
return &types.QueryShareImageResponse{
List: ResultList}, nil
}
func (l *QueryShareImageLogic) recordUserVisit(shareID int64, userID string) error {
// 查询是否已经存在该用户对该分享的访问记录
var visitRecord model.ScaStorageShareVisit
scaStorageShareVisit := l.svcCtx.DB.ScaStorageShareVisit
_, err := scaStorageShareVisit.
Where(scaStorageShareVisit.ShareID.Eq(shareID), scaStorageShareVisit.UserID.Eq(userID)).
First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// 如果记录不存在,创建新的访问记录
visitRecord = model.ScaStorageShareVisit{
UserID: userID,
ShareID: shareID,
Views: 1,
}
err = l.svcCtx.DB.ScaStorageShareVisit.Create(&visitRecord)
if err != nil {
return errors.New("failed to create visit record")
}
return nil
}
return errors.New("failed to query visit record")
}
// 如果记录存在,增加访问次数
info, err := scaStorageShareVisit.
Where(scaStorageShareVisit.UserID.Eq(userID), scaStorageShareVisit.ShareID.Eq(shareID)).
Update(scaStorageShareVisit.Views, scaStorageShareVisit.Views.Add(1))
if err != nil {
return errors.New("failed to update visit record")
}
if info.RowsAffected == 0 {
return errors.New("failed to update visit record")
}
return nil
}
func (l *QueryShareImageLogic) incrementVisitCount(shareCode string, limit int64) error {
// Redis 键值
cacheKey := constant.ImageShareVisitPrefix + shareCode
currentVisitCount, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Int64()
if err != nil && !errors.Is(err, redis.Nil) {
return err
}
// 如果访问次数超过限制,返回错误
if currentVisitCount >= limit {
return errors.New("access limit reached")
}
// 增加访问次数
err = l.svcCtx.RedisClient.Incr(l.ctx, cacheKey).Err()
if err != nil {
return err
}
return nil
}
// 提取解密操作为函数
func (l *QueryShareImageLogic) decryptConfig(config *model.ScaStorageConfig) (*storageConfig.StorageConfig, error) {
accessKey, err := encrypt.Decrypt(config.AccessKey, l.svcCtx.Config.Encrypt.Key)
if err != nil {
return nil, errors.New("decrypt access key failed")
}
secretKey, err := encrypt.Decrypt(config.SecretKey, l.svcCtx.Config.Encrypt.Key)
if err != nil {
return nil, errors.New("decrypt secret key failed")
}
return &storageConfig.StorageConfig{
Provider: config.Provider,
Endpoint: config.Endpoint,
AccessKey: accessKey,
SecretKey: secretKey,
BucketName: config.Bucket,
Region: config.Region,
}, nil
}
// 从缓存或数据库中获取 OSS 配置
func (l *QueryShareImageLogic) getOssConfigFromCacheOrDb(cacheKey, uid, provider string) (*storageConfig.StorageConfig, error) {
result, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
if err != nil && !errors.Is(err, redis.Nil) {
return nil, errors.New("get oss config failed")
}
var ossConfig *storageConfig.StorageConfig
if result != "" {
var redisOssConfig model.ScaStorageConfig
if err = json.Unmarshal([]byte(result), &redisOssConfig); err != nil {
return nil, errors.New("unmarshal oss config failed")
}
return l.decryptConfig(&redisOssConfig)
}
// 缓存未命中,从数据库中加载
scaOssConfig := l.svcCtx.DB.ScaStorageConfig
dbOssConfig, err := scaOssConfig.Where(scaOssConfig.UserID.Eq(uid), scaOssConfig.Provider.Eq(provider)).First()
if err != nil {
return nil, err
}
// 缓存数据库配置
ossConfig, err = l.decryptConfig(dbOssConfig)
if err != nil {
return nil, err
}
marshalData, err := json.Marshal(dbOssConfig)
if err != nil {
return nil, errors.New("marshal oss config failed")
}
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshalData, 0).Err()
if err != nil {
return nil, errors.New("set oss config failed")
}
return ossConfig, nil
}

View File

@@ -0,0 +1,347 @@
package share
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/ccpwcn/kgo"
"github.com/minio/minio-go/v7"
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/core/logx"
"golang.org/x/sync/errgroup"
"image"
"path"
"path/filepath"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/app/auth/model/mysql/query"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/encrypt"
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
"strconv"
"time"
)
type UploadShareImageLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadShareImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadShareImageLogic {
return &UploadShareImageLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UploadShareImageLogic) UploadShareImage(req *types.ShareImageRequest) (resp string, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return "", errors.New("user_id not found")
}
// 启动事务,确保插入操作的原子性
tx := l.svcCtx.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback() // 如果有panic发生回滚事务
logx.Errorf("transaction rollback: %v", r)
}
}()
albumName := req.Title
if albumName == "" {
albumName = "快传照片"
}
// 创建一个相册
album := model.ScaStorageAlbum{
UserID: uid,
AlbumName: albumName,
CoverImage: req.Images[0].Thumbnail,
AlbumType: constant.AlbumTypeShared,
}
err = tx.ScaStorageAlbum.Create(&album)
if err != nil {
return "", err
}
var g errgroup.Group
// 为每张图片启动一个协程
for _, img := range req.Images {
img := img // 确保每个协程有独立的 img 参数副本
g.Go(func() error {
return l.uploadImageAndRecord(tx, uid, album, img, req)
})
}
// 等待所有任务完成并返回第一个错误
if err = g.Wait(); err != nil {
tx.Rollback()
return "", err
}
duration, err := strconv.Atoi(req.ExpireDate)
if err != nil {
return "", errors.New("invalid expire date")
}
expiryTime := l.GenerateExpiryTime(time.Now(), duration)
storageShare := model.ScaStorageShare{
UserID: uid,
AlbumID: album.ID,
ShareCode: kgo.SimpleUuid(),
Status: 0,
AccessPassword: req.AccessPassword,
VisitLimit: req.AccessLimit,
ValidityPeriod: int64(duration),
ExpireTime: expiryTime,
}
err = tx.ScaStorageShare.Create(&storageShare)
if err != nil {
tx.Rollback()
return "", err
}
// 缓存分享码
marshal, err := json.Marshal(storageShare)
if err != nil {
tx.Rollback()
return "", err
}
cacheKey := constant.ImageSharePrefix + storageShare.ShareCode
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshal, time.Duration(duration)*time.Hour*24).Err()
if err != nil {
tx.Rollback()
return "", err
}
// 提交事务
if err = tx.Commit(); err != nil {
tx.Rollback()
logx.Errorf("Transaction commit failed: %v", err)
return "", err
}
return storageShare.ShareCode, nil
}
func (l *UploadShareImageLogic) uploadImageAndRecord(tx *query.QueryTx, uid string, album model.ScaStorageAlbum, img types.ShareImageMeta, req *types.ShareImageRequest) error {
// 上传缩略图到 Minio
thumbnail, err := base64.StdEncoding.DecodeString(img.Thumbnail)
if err != nil {
return fmt.Errorf("base64 decode failed: %v", err)
}
thumbObjectKey := path.Join(
uid,
time.Now().Format("2006/01"),
l.classifyFile(img.FileType),
fmt.Sprintf("%s_%s.jpg", time.Now().Format("20060102150405"), kgo.SimpleUuid()),
)
exists, err := l.svcCtx.MinioClient.BucketExists(l.ctx, constant.ThumbnailBucketName)
if err != nil || !exists {
err = l.svcCtx.MinioClient.MakeBucket(l.ctx, constant.ThumbnailBucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
if err != nil {
logx.Errorf("Failed to create MinIO bucket: %v", err)
return err
}
}
_, err = l.svcCtx.MinioClient.PutObject(
l.ctx,
constant.ThumbnailBucketName,
thumbObjectKey,
bytes.NewReader(thumbnail),
int64(len(thumbnail)),
minio.PutObjectOptions{
ContentType: "image/jpeg",
},
)
if err != nil {
logx.Errorf("Failed to upload MinIO object: %v", err)
return err
}
// 记录缩略图
thumbRecord := model.ScaStorageThumb{
UserID: uid,
ThumbPath: thumbObjectKey,
ThumbW: img.ThumbW,
ThumbH: img.ThumbH,
ThumbSize: float64(len(thumbnail)),
}
err = tx.ScaStorageThumb.Create(&thumbRecord)
if err != nil {
return err
}
// 上传原始图片到用户的存储桶
originImage, err := base64.StdEncoding.DecodeString(img.OriginImage)
if err != nil {
return fmt.Errorf("base64 decode failed: %v", err)
}
originObjectKey := path.Join(
"share_space",
uid,
time.Now().Format("2006/01"),
fmt.Sprintf("%s_%s%s", img.FileName, kgo.SimpleUuid(), filepath.Ext(img.FileName)),
)
// 获取存储服务
ossConfig, err := l.getOssConfigFromCacheOrDb(constant.UserOssConfigPrefix+uid+":"+req.Provider, uid, req.Provider)
if err != nil {
return err
}
service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
if err != nil {
return errors.New("get storage failed")
}
_, err = service.UploadFileSimple(l.ctx, ossConfig.BucketName, originObjectKey, bytes.NewReader(originImage), map[string]string{
"Content-Type": img.FileType,
})
if err != nil {
logx.Errorf("Failed to upload object to storage: %v", err)
return err
}
// 获取图片信息
width, height, size, err := l.GetImageInfo(img.OriginImage)
if err != nil {
return err
}
// 记录原始图片信息
imageRecord := model.ScaStorageInfo{
UserID: uid,
Provider: req.Provider,
Bucket: req.Bucket,
Path: originObjectKey,
FileName: img.FileName,
FileSize: strconv.Itoa(size),
FileType: img.FileType,
Width: float64(width),
Height: float64(height),
Type: constant.ImageTypeShared,
AlbumID: album.ID,
ThumbID: thumbRecord.ID,
}
err = tx.ScaStorageInfo.Create(&imageRecord)
if err != nil {
return err
}
return nil
}
func (l *UploadShareImageLogic) GetImageInfo(base64Str string) (width, height int, size int, err error) {
// 解码 Base64
data, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil {
return 0, 0, 0, fmt.Errorf("base64 decode failed: %v", err)
}
// 获取图片大小
size = len(data)
// 解析图片宽高
reader := bytes.NewReader(data)
imgCfg, _, err := image.DecodeConfig(reader)
if err != nil {
return 0, 0, 0, fmt.Errorf("decode image config failed: %v", err)
}
return imgCfg.Width, imgCfg.Height, size, nil
}
// 提取解密操作为函数
func (l *UploadShareImageLogic) decryptConfig(config *model.ScaStorageConfig) (*storageConfig.StorageConfig, error) {
accessKey, err := encrypt.Decrypt(config.AccessKey, l.svcCtx.Config.Encrypt.Key)
if err != nil {
return nil, errors.New("decrypt access key failed")
}
secretKey, err := encrypt.Decrypt(config.SecretKey, l.svcCtx.Config.Encrypt.Key)
if err != nil {
return nil, errors.New("decrypt secret key failed")
}
return &storageConfig.StorageConfig{
Provider: config.Provider,
Endpoint: config.Endpoint,
AccessKey: accessKey,
SecretKey: secretKey,
BucketName: config.Bucket,
Region: config.Region,
}, nil
}
// 从缓存或数据库中获取 OSS 配置
func (l *UploadShareImageLogic) getOssConfigFromCacheOrDb(cacheKey, uid, provider string) (*storageConfig.StorageConfig, error) {
result, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
if err != nil && !errors.Is(err, redis.Nil) {
return nil, errors.New("get oss config failed")
}
var ossConfig *storageConfig.StorageConfig
if result != "" {
var redisOssConfig model.ScaStorageConfig
if err = json.Unmarshal([]byte(result), &redisOssConfig); err != nil {
return nil, errors.New("unmarshal oss config failed")
}
return l.decryptConfig(&redisOssConfig)
}
// 缓存未命中,从数据库中加载
scaOssConfig := l.svcCtx.DB.ScaStorageConfig
dbOssConfig, err := scaOssConfig.Where(scaOssConfig.UserID.Eq(uid), scaOssConfig.Provider.Eq(provider)).First()
if err != nil {
return nil, err
}
// 缓存数据库配置
ossConfig, err = l.decryptConfig(dbOssConfig)
if err != nil {
return nil, err
}
marshalData, err := json.Marshal(dbOssConfig)
if err != nil {
return nil, errors.New("marshal oss config failed")
}
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshalData, 0).Err()
if err != nil {
return nil, errors.New("set oss config failed")
}
return ossConfig, nil
}
// GenerateExpiryTime 函数接受当前时间和有效期(天为单位),返回过期时间
func (l *UploadShareImageLogic) GenerateExpiryTime(currentTime time.Time, durationInDays int) time.Time {
// 创建一个持续时间对象
duration := time.Duration(durationInDays) * 24 * time.Hour
// 将当前时间加上持续时间,得到过期时间
expiryTime := currentTime.Add(duration)
return expiryTime
}
func (l *UploadShareImageLogic) classifyFile(mimeType string) string {
// 使用map存储MIME类型及其对应的分类
typeMap := map[string]string{
"image/jpeg": "image",
"image/png": "image",
"image/gif": "gif",
"image/bmp": "image",
"image/tiff": "image",
"image/webp": "image",
"video/mp4": "video",
"video/avi": "video",
"video/mpeg": "video",
"video/quicktime": "video",
"video/x-msvideo": "video",
"video/x-flv": "video",
"video/x-matroska": "video",
}
// 根据MIME类型从map中获取分类
if classification, exists := typeMap[mimeType]; exists {
return classification
}
return "other"
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
@@ -33,7 +34,7 @@ func (l *CreateAlbumLogic) CreateAlbum(req *types.AlbumCreateRequest) (resp *typ
storageAlbum := &model.ScaStorageAlbum{
UserID: uid,
AlbumName: req.Name,
AlbumType: "0",
AlbumType: constant.AlbumTypeMine,
}
err = l.svcCtx.DB.ScaStorageAlbum.Create(storageAlbum)
if err != nil {

View File

@@ -68,6 +68,7 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
storageInfo.FileName,
storageInfo.CreatedAt,
storageThumb.ThumbPath,
storageInfo.Path,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
@@ -86,17 +87,17 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
return &types.AlbumDetailListResponse{}, nil
}
//// 加载用户oss配置信息
//cacheOssConfigKey := constant.UserOssConfigPrefix + uid + ":" + req.Provider
//ossConfig, err := l.getOssConfigFromCacheOrDb(cacheOssConfigKey, uid, req.Provider)
//if err != nil {
// return nil, err
//}
//
//service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
//if err != nil {
// return nil, errors.New("get storage failed")
//}
// 加载用户oss配置信息
cacheOssConfigKey := constant.UserOssConfigPrefix + uid + ":" + req.Provider
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheOssConfigKey, uid, req.Provider)
if err != nil {
return nil, err
}
service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
if err != nil {
return nil, errors.New("get storage failed")
}
// 按日期进行分组
var wg sync.WaitGroup
@@ -114,6 +115,11 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
logx.Error(err)
return
}
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Hour*24*7)
if err != nil {
logx.Error(err)
return
}
// 使用 Load 或 Store 确保原子操作
value, _ := groupedImages.LoadOrStore(date, []types.ImageMeta{})
images := value.([]types.ImageMeta)
@@ -121,7 +127,8 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
URL: presignedUrl.String(),
Thumbnail: presignedUrl.String(),
URL: url,
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),

View File

@@ -3,6 +3,7 @@ package storage
import (
"context"
"errors"
"gorm.io/gen"
"gorm.io/gen/field"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
@@ -37,7 +38,15 @@ func (l *GetAlbumListLogic) GetAlbumList(req *types.AlbumListRequest) (resp *typ
} else {
orderConditions = append(orderConditions, storageAlbum.AlbumName.Desc())
}
albums, err := storageAlbum.Where(storageAlbum.UserID.Eq(uid), storageAlbum.AlbumType.Eq(req.Type)).Order(orderConditions...).Find()
var typeConditions []gen.Condition
if req.Type != -1 {
// 获取全部相册
typeConditions = append(typeConditions, storageAlbum.AlbumType.Eq(req.Type))
typeConditions = append(typeConditions, storageAlbum.UserID.Eq(uid))
}
albums, err := storageAlbum.Where(
typeConditions...).
Order(orderConditions...).Find()
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,80 @@
package storage
import (
"context"
"errors"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserStorageListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUserStorageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserStorageListLogic {
return &GetUserStorageListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// providerNameMap 存储商映射表
var providerNameMap = map[string]string{
"ali": "阿里云OSS",
"tencent": "腾讯云COS",
"aws": "Amazon S3",
"qiniu": "七牛云",
"huawei": "华为云OBS",
}
func (l *GetUserStorageListLogic) GetUserStorageList() (resp *types.StorageListResponse, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return nil, errors.New("user_id not found")
}
storageConfig := l.svcCtx.DB.ScaStorageConfig
storageConfigs, err := storageConfig.Select(
storageConfig.Provider,
storageConfig.Bucket).
Where(
storageConfig.UserID.Eq(uid)).Find()
if err != nil {
return nil, err
}
// 使用 map 组织数据
providerMap := make(map[string][]types.StorageMeta)
for _, config := range storageConfigs {
providerMap[config.Provider] = append(providerMap[config.Provider], types.StorageMeta{
Value: config.Bucket,
Name: config.Bucket,
})
}
// 组装返回结构
var records []types.StroageNode
for provider, buckets := range providerMap {
records = append(records, types.StroageNode{
Value: provider,
Name: l.getProviderName(provider),
Children: buckets,
})
}
// 返回数据
return &types.StorageListResponse{
Records: records,
}, nil
}
// getProviderName 获取存储商的中文名称
func (l *GetUserStorageListLogic) getProviderName(provider string) string {
if name, exists := providerNameMap[provider]; exists {
return name
}
return provider
}

View File

@@ -288,6 +288,7 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
}
objectKey := path.Join(
"image_space",
uid,
time.Now().Format("2006/01"), // 按年/月划分目录
l.classifyFile(result.FileType, result.IsScreenshot),