optimized image list interface

This commit is contained in:
2025-02-17 11:21:38 +08:00
parent ab4e9c4d59
commit b196e50aee
72 changed files with 1676 additions and 343 deletions

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"github.com/redis/go-redis/v9"
"math/rand"
"net/url"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/app/auth/model/mysql/query"
"schisandra-album-cloud-microservices/common/constant"
@@ -56,18 +57,28 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
return nil, errors.New("get cached image list failed")
}
// 缓存未命中,从数据库中查询
storageInfo := l.svcCtx.DB.ScaStorageInfo
storageThumb := l.svcCtx.DB.ScaStorageThumb
// 数据库查询文件信息列表
var storageInfoQuery query.IScaStorageInfoDo
var storageInfoList []types.FileInfoResult
storageInfoQuery = storageInfo.Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.AlbumID.Eq(req.ID)).
storageInfoQuery = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageThumb.ThumbPath,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.AlbumID.Eq(req.ID)).
Order(storageInfo.CreatedAt.Desc())
storageInfoList, err := storageInfoQuery.Find()
err = storageInfoQuery.Scan(&storageInfoList)
if err != nil {
return nil, err
}
@@ -75,17 +86,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
@@ -93,11 +104,12 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
for _, dbFileInfo := range storageInfoList {
wg.Add(1)
go func(dbFileInfo *model.ScaStorageInfo) {
go func(dbFileInfo *types.FileInfoResult) {
defer wg.Done()
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Hour*24*7)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, dbFileInfo.ThumbPath, time.Hour*24*7, reqParams)
if err != nil {
logx.Error(err)
return
@@ -109,15 +121,15 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
URL: url,
Width: dbFileInfo.Width,
Height: dbFileInfo.Height,
URL: presignedUrl.String(),
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
})
// 重新存储更新后的图像列表
groupedImages.Store(date, images)
}(dbFileInfo)
}(&dbFileInfo)
}
wg.Wait()
var imageList []types.AllImageDetail

View File

@@ -7,8 +7,8 @@ import (
"fmt"
"github.com/redis/go-redis/v9"
"math/rand"
"net/url"
"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"
@@ -58,16 +58,24 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
// 缓存未命中,从数据库中查询
storageInfo := l.svcCtx.DB.ScaStorageInfo
// 数据库查询文件信息列表
var storageInfoQuery query.IScaStorageInfoDo
storageThumb := l.svcCtx.DB.ScaStorageThumb
var storageInfoList []types.FileInfoResult
storageInfoQuery = storageInfo.Where(
err = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageInfo.Path,
storageThumb.ThumbPath,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.FaceID.Eq(req.FaceID)).
Order(storageInfo.CreatedAt.Desc())
storageInfoList, err := storageInfoQuery.Find()
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
if err != nil {
return nil, err
}
@@ -93,15 +101,22 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
for _, dbFileInfo := range storageInfoList {
wg.Add(1)
go func(dbFileInfo *model.ScaStorageInfo) {
go func(dbFileInfo *types.FileInfoResult) {
defer wg.Done()
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, dbFileInfo.ThumbPath, time.Hour*24*7, reqParams)
if err != nil {
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)
@@ -109,15 +124,16 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
Thumbnail: presignedUrl.String(),
URL: url,
Width: dbFileInfo.Width,
Height: dbFileInfo.Height,
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
})
// 重新存储更新后的图像列表
groupedImages.Store(date, images)
}(dbFileInfo)
}(&dbFileInfo)
}
wg.Wait()
var imageList []types.AllImageDetail

View File

@@ -0,0 +1,148 @@
package storage
import (
"context"
"encoding/json"
"errors"
"github.com/redis/go-redis/v9"
"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"
"strconv"
"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 GetImageUrlLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetImageUrlLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetImageUrlLogic {
return &GetImageUrlLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetImageUrlLogic) GetImageUrl(req *types.SingleImageRequest) (resp string, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return "", errors.New("user_id not found")
}
// 从redis 获取url
cacheKey := constant.ImageSinglePrefix + uid + ":" + strconv.FormatInt(req.ID, 10)
cacheUrl, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
if err != nil && !errors.Is(err, redis.Nil) {
logx.Info(err)
return "", errors.New("get image url failed")
}
if cacheUrl != "" {
return cacheUrl, nil
}
storageInfo := l.svcCtx.DB.ScaStorageInfo
result, err := storageInfo.Select(
storageInfo.ID,
storageInfo.Provider,
storageInfo.Bucket,
storageInfo.Path).
Where(storageInfo.ID.Eq(req.ID), storageInfo.UserID.Eq(uid)).
First()
if err != nil {
logx.Info(err)
return "", errors.New("get storage info failed")
}
if result == nil {
return "", errors.New("get storage info failed")
}
// 加载用户oss配置信息
cacheOssConfigKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheOssConfigKey, uid, result.Provider)
if err != nil {
return "", err
}
service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
if err != nil {
return "", errors.New("get storage failed")
}
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, result.Path, 7*24*time.Hour)
if err != nil {
return "", errors.New("get presigned url failed")
}
// 缓存url
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, url, 7*24*time.Hour).Err()
if err != nil {
logx.Info(err)
}
return url, nil
}
// 提取解密操作为函数
func (l *GetImageUrlLogic) 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 *GetImageUrlLogic) 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

@@ -8,10 +8,10 @@ import (
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/core/logx"
"math/rand"
"net/url"
"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"
@@ -66,25 +66,44 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
// 缓存未命中,从数据库中查询
storageInfo := l.svcCtx.DB.ScaStorageInfo
// 数据库查询文件信息列表
var storageInfoQuery query.IScaStorageInfoDo
storageThumb := l.svcCtx.DB.ScaStorageThumb
var storageInfoList []types.FileInfoResult
if req.Sort {
storageInfoQuery = storageInfo.Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Eq(req.Type),
storageInfo.AlbumID.IsNull()).
Order(storageInfo.CreatedAt.Desc())
err = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageInfo.Path,
storageThumb.ThumbPath,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Eq(req.Type),
storageInfo.AlbumID.IsNull()).
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
} else {
storageInfoQuery = storageInfo.Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Eq(req.Type)).
Order(storageInfo.CreatedAt.Desc())
err = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageThumb.ThumbPath,
storageInfo.Path,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Eq(req.Type)).
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
}
storageInfoList, err := storageInfoQuery.Find()
if err != nil {
return nil, err
}
@@ -110,10 +129,16 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
for _, dbFileInfo := range storageInfoList {
wg.Add(1)
go func(dbFileInfo *model.ScaStorageInfo) {
go func(dbFileInfo *types.FileInfoResult) {
defer wg.Done()
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, dbFileInfo.ThumbPath, time.Hour*24*7, reqParams)
if err != nil {
logx.Error(err)
return
}
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Hour*24*7)
if err != nil {
logx.Error(err)
@@ -126,15 +151,16 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
Thumbnail: presignedUrl.String(),
URL: url,
Width: dbFileInfo.Width,
Height: dbFileInfo.Height,
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
})
// 重新存储更新后的图像列表
groupedImages.Store(date, images)
}(dbFileInfo)
}(&dbFileInfo)
}
wg.Wait()
var imageList []types.AllImageDetail

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"github.com/redis/go-redis/v9"
"math/rand"
"net/url"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/app/auth/model/mysql/query"
"schisandra-album-cloud-microservices/common/constant"
@@ -56,18 +57,28 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
return nil, errors.New("get cached image list failed")
}
// 缓存未命中,从数据库中查询
storageInfo := l.svcCtx.DB.ScaStorageInfo
storageThumb := l.svcCtx.DB.ScaStorageThumb
// 数据库查询文件信息列表
var storageInfoQuery query.IScaStorageInfoDo
var storageInfoList []types.FileInfoResult
storageInfoQuery = storageInfo.Where(
storageInfoQuery = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageThumb.ThumbPath,
storageInfo.Path,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.LocationID.Eq(req.ID)).
Order(storageInfo.CreatedAt.Desc())
storageInfoList, err := storageInfoQuery.Find()
err = storageInfoQuery.Scan(&storageInfoList)
if err != nil {
return nil, err
}
@@ -93,10 +104,16 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
for _, dbFileInfo := range storageInfoList {
wg.Add(1)
go func(dbFileInfo *model.ScaStorageInfo) {
go func(dbFileInfo *types.FileInfoResult) {
defer wg.Done()
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, dbFileInfo.ThumbPath, time.Hour*24*7, reqParams)
if err != nil {
logx.Error(err)
return
}
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Hour*24*7)
if err != nil {
logx.Error(err)
@@ -109,15 +126,16 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
Thumbnail: presignedUrl.String(),
URL: url,
Width: dbFileInfo.Width,
Height: dbFileInfo.Height,
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
})
// 重新存储更新后的图像列表
groupedImages.Store(date, images)
}(dbFileInfo)
}(&dbFileInfo)
}
wg.Wait()
var imageList []types.AllImageDetail

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"github.com/redis/go-redis/v9"
"net/url"
"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"
@@ -53,16 +54,16 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
}
// 加载用户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")
}
//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")
//}
locationMap := make(map[string][]types.LocationMeta)
for _, loc := range locations {
@@ -77,7 +78,8 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
if city == "" {
city = loc.Country
}
url, err := service.PresignedURL(l.ctx, req.Bucket, loc.CoverImage, 7*24*time.Hour)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, loc.CoverImage, 7*24*time.Hour, reqParams)
if err != nil {
return nil, errors.New("get presigned url failed")
}
@@ -85,7 +87,7 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
ID: loc.ID,
City: city,
Total: loc.Total,
CoverImage: url,
CoverImage: presignedUrl.String(),
}
locationMap[locationKey] = append(locationMap[locationKey], locationMeta)
}

View File

@@ -62,37 +62,34 @@ func (l *QueryRecentImageListLogic) QueryRecentImageList() (resp *types.RecentLi
if cmd == nil {
continue
}
val, ok := cmd.(string)
if !ok {
logx.Error("invalid value type")
return nil, errors.New("invalid value type")
}
var imageMeta types.ImageMeta
err = json.Unmarshal([]byte(val), &imageMeta)
if err != nil {
logx.Error(err)
return nil, errors.New("unmarshal recent file list failed")
}
parse, err := time.Parse("2006-01-02 15:04:05", imageMeta.CreatedAt)
if err != nil {
logx.Error(err)
return nil, errors.New("parse recent file list failed")
}
date := parse.Format("2006年1月2日 星期" + WeekdayMap[parse.Weekday()])
// 使用LoadOrStore来检查并存储或者追加
wg.Add(1)
go func(cmd interface{}) {
go func(date string, imageMeta types.ImageMeta) {
defer wg.Done()
val, ok := cmd.(string)
if !ok {
logx.Error("invalid value type")
return
value, loaded := groupedImages.LoadOrStore(date, []types.ImageMeta{imageMeta})
if loaded {
images := value.([]types.ImageMeta)
images = append(images, imageMeta)
groupedImages.Store(date, images)
}
var imageMeta types.ImageMeta
err = json.Unmarshal([]byte(val), &imageMeta)
if err != nil {
logx.Error(err)
return
}
parse, err := time.Parse("2006-01-02 15:04:05", imageMeta.CreatedAt)
if err != nil {
logx.Error(err)
return
}
date := parse.Format("2006年1月2日 星期" + WeekdayMap[parse.Weekday()])
groupedImages.Range(func(key, value interface{}) bool {
if key == date {
images := value.([]types.ImageMeta)
images = append(images, imageMeta)
groupedImages.Store(date, images)
return false
}
return true
})
groupedImages.Store(date, []types.ImageMeta{imageMeta})
}(cmd)
}(date, imageMeta)
}
wg.Wait()

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"github.com/redis/go-redis/v9"
"math/rand"
"net/url"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/app/auth/model/mysql/query"
"schisandra-album-cloud-microservices/common/constant"
@@ -56,18 +57,29 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
return nil, errors.New("get cached image list failed")
}
// 缓存未命中,从数据库中查询
storageInfo := l.svcCtx.DB.ScaStorageInfo
storageThumb := l.svcCtx.DB.ScaStorageThumb
// 数据库查询文件信息列表
var storageInfoQuery query.IScaStorageInfoDo
var storageInfoList []types.FileInfoResult
storageInfoQuery = storageInfo.Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Tag.Eq(req.TagName)).
storageInfoQuery = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageThumb.ThumbPath,
storageInfo.Path,
storageThumb.ThumbW,
storageThumb.ThumbH,
storageThumb.ThumbSize).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
Where(
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Tag.Eq(req.TagName)).
Order(storageInfo.CreatedAt.Desc())
storageInfoList, err := storageInfoQuery.Find()
err = storageInfoQuery.Scan(&storageInfoList)
if err != nil {
return nil, err
}
@@ -93,10 +105,16 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
for _, dbFileInfo := range storageInfoList {
wg.Add(1)
go func(dbFileInfo *model.ScaStorageInfo) {
go func(dbFileInfo *types.FileInfoResult) {
defer wg.Done()
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, dbFileInfo.ThumbPath, time.Hour*24*7, reqParams)
if err != nil {
logx.Error(err)
return
}
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Hour*24*7)
if err != nil {
logx.Error(err)
@@ -109,15 +127,16 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
Thumbnail: presignedUrl.String(),
URL: url,
Width: dbFileInfo.Width,
Height: dbFileInfo.Height,
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
})
// 重新存储更新后的图像列表
groupedImages.Store(date, images)
}(dbFileInfo)
}(&dbFileInfo)
}
wg.Wait()
var imageList []types.AllImageDetail

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"github.com/redis/go-redis/v9"
"net/url"
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
"schisandra-album-cloud-microservices/common/constant"
"schisandra-album-cloud-microservices/common/encrypt"
@@ -38,12 +39,15 @@ func (l *QueryThingImageListLogic) QueryThingImageList(req *types.ThingListReque
return nil, errors.New("user_id not found")
}
storageInfo := l.svcCtx.DB.ScaStorageInfo
storageInfos, err := storageInfo.Select(
storageThumb := l.svcCtx.DB.ScaStorageThumb
var thingList []types.ThingImageList
err = storageInfo.Select(
storageInfo.ID,
storageInfo.Category,
storageInfo.Tag,
storageInfo.Path,
storageThumb.ThumbPath,
storageInfo.CreatedAt).
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
Where(storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
@@ -52,28 +56,28 @@ func (l *QueryThingImageListLogic) QueryThingImageList(req *types.ThingListReque
storageInfo.Category.Length().Gt(0),
storageInfo.Tag.Length().Gte(0)).
Order(storageInfo.CreatedAt.Desc()).
Find()
Scan(&thingList)
if err != nil {
return nil, err
}
// 加载用户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")
}
//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")
//}
categoryMap := sync.Map{}
tagCountMap := sync.Map{}
tagCoverMap := sync.Map{} // 用于存储每个 Tag 的封面图片路径
for _, info := range storageInfos {
for _, info := range thingList {
tagKey := info.Category + "::" + info.Tag
if _, exists := tagCountMap.Load(tagKey); !exists {
tagCountMap.Store(tagKey, int64(0))
@@ -90,14 +94,15 @@ func (l *QueryThingImageListLogic) QueryThingImageList(req *types.ThingListReque
// 为每个 Tag 存储封面图片路径
if _, exists := tagCoverMap.Load(tagKey); !exists {
// 使用服务生成预签名 URL
coverImageURL, err := service.PresignedURL(l.ctx, req.Bucket, info.Path, 7*24*time.Hour)
reqParams := make(url.Values)
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, info.ThumbPath, time.Hour*24*7, reqParams)
if err == nil {
tagCoverMap.Store(tagKey, coverImageURL)
tagCoverMap.Store(tagKey, presignedUrl.String())
}
}
}
var thingListData []types.ThingListData
var thingListResponse []types.ThingListData
categoryMap.Range(func(category, tagData interface{}) bool {
var metas []types.ThingMeta
tagData.(*sync.Map).Range(func(tag, item interface{}) bool {
@@ -113,14 +118,14 @@ func (l *QueryThingImageListLogic) QueryThingImageList(req *types.ThingListReque
metas = append(metas, meta)
return true
})
thingListData = append(thingListData, types.ThingListData{
thingListResponse = append(thingListResponse, types.ThingListData{
Category: category.(string),
List: metas,
})
return true
})
return &types.ThingListResponse{Records: thingListData}, nil
return &types.ThingListResponse{Records: thingListResponse}, nil
}
// 提取解密操作为函数

View File

@@ -1,17 +1,23 @@
package storage
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"
"golang.org/x/sync/semaphore"
"gorm.io/gorm"
"io"
"mime/multipart"
"net/http"
"net/url"
"path"
"path/filepath"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
@@ -56,72 +62,159 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
if err != nil {
return "", err
}
defer file.Close()
defer func(file multipart.File) {
_ = file.Close()
}(file)
// 解析 AI 识别结果
result, err := l.parseAIRecognitionResult(r)
if err != nil {
return "", err
}
var faceId int64 = 0
bytes, err := io.ReadAll(file)
data, err := io.ReadAll(file)
if err != nil {
return "", err
}
// 人脸识别
if result.FileType == "image/png" || result.FileType == "image/jpeg" {
face, err := l.svcCtx.AiSvcRpc.FaceRecognition(l.ctx, &pb.FaceRecognitionRequest{Face: bytes, UserId: uid})
if err != nil {
return "", err
// 解析上传的缩略图
thumbnail, _, err := l.getUploadedThumbnail(r)
if err != nil {
return "", err
}
defer thumbnail.Close()
// 解析图片信息识别结果
result, err := l.parseImageInfoResult(r)
if err != nil {
return "", err
}
// 使用 `errgroup.Group` 处理并发任务
var (
faceId int64
filePath string
minioFilePath string
presignedURL string
)
g, ctx := errgroup.WithContext(context.Background())
// 创建信号量,限制最大并发上传数(比如最多同时 5 个任务)
sem := semaphore.NewWeighted(5)
// 进行人脸识别
g.Go(func() error {
if result.FileType == "image/png" || result.FileType == "image/jpeg" {
face, err := l.svcCtx.AiSvcRpc.FaceRecognition(l.ctx, &pb.FaceRecognitionRequest{
Face: data,
UserId: uid,
})
if err != nil {
return err
}
if face != nil {
faceId = face.GetFaceId()
}
}
if face != nil {
faceId = face.GetFaceId()
}
}
// 根据 GPS 信息获取地理位置信息
country, province, city, err := l.getGeoLocation(result.Latitude, result.Longitude)
if err != nil {
return "", err
}
return nil
})
// 上传文件到 OSS
// 重新设置文件指针到文件开头
if _, err = file.Seek(0, 0); err != nil {
return "", err
}
bucket, provider, filePath, url, err := l.uploadFileToOSS(uid, header, file, result)
if err != nil {
g.Go(func() error {
if err := sem.Acquire(ctx, 1); err != nil {
return err
}
defer sem.Release(1)
// 重新创建 `multipart.File` 兼容的 `Reader`
fileReader := struct {
*bytes.Reader
io.Closer
}{
Reader: bytes.NewReader(data),
Closer: io.NopCloser(nil),
}
fileUrl, err := l.uploadFileToOSS(uid, header, fileReader, result)
if err != nil {
return err
}
filePath = fileUrl
return nil
})
// 上传缩略图到 MinIO
g.Go(func() error {
if err := sem.Acquire(ctx, 1); err != nil {
return err
}
defer sem.Release(1)
path, url, err := l.uploadFileToMinio(uid, header, thumbnail, result)
if err != nil {
return err
}
minioFilePath = path
presignedURL = url
return nil
})
// 等待所有 goroutine 执行完毕
if err = g.Wait(); err != nil {
return "", err
}
// 将地址信息保存到数据库
locationId, err := l.saveFileLocationInfoToDB(uid, result.Provider, result.Bucket, result.Latitude, result.Longitude, country, province, city, filePath)
fileUploadMessage := &types.FileUploadMessage{
UID: uid,
Data: result,
FaceID: faceId,
FileHeader: header,
FilePath: filePath,
PresignedURL: presignedURL,
ThumbPath: minioFilePath,
}
// 转换为 JSON
messageData, err := json.Marshal(fileUploadMessage)
if err != nil {
return "", err
}
err = l.svcCtx.NSQProducer.Publish(constant.MQTopicImageProcess, messageData)
if err != nil {
return "", errors.New("publish message failed")
}
// 将 EXIF 和文件信息存入数据库
id, err := l.saveFileInfoToDB(uid, bucket, provider, header, result, locationId, faceId, filePath)
if err != nil {
return "", err
}
// 删除缓存
l.afterImageUpload(uid, provider, bucket)
// ------------------------------------------------------------------------
// redis 保存最近7天上传的文件列表
err = l.saveRecentFileList(uid, url, id, result, header.Filename)
if err != nil {
return "", err
}
//// 根据 GPS 信息获取地理位置信息
//country, province, city, err := l.getGeoLocation(result.Latitude, result.Longitude)
//if err != nil {
// return "", err
//}
//// 将地址信息保存到数据库
//locationId, err := l.saveFileLocationInfoToDB(uid, result.Provider, result.Bucket, result.Latitude, result.Longitude, country, province, city, filePath)
//if err != nil {
// return "", err
//}
//
//// 将 EXIF 和文件信息存入数据库
//id, err := l.saveFileInfoToDB(uid, bucket, provider, header, result, locationId, faceId, filePath)
//if err != nil {
// return "", err
//}
//// 删除缓存
//l.afterImageUpload(uid, provider, bucket)
//
//// redis 保存最近7天上传的文件列表
//err = l.saveRecentFileList(uid, url, id, result, header.Filename)
//if err != nil {
// return "", err
//}
return "success", nil
}
// 将 multipart.File 转为 Base64 字符串
func (l *UploadFileLogic) fileToBase64(file multipart.File) (string, error) {
// 读取文件内容
fileBytes, err := io.ReadAll(file)
if err != nil {
return "", err
}
// 将文件内容转为 Base64 编码
return base64.StdEncoding.EncodeToString(fileBytes), nil
}
// 获取用户 ID
func (l *UploadFileLogic) getUserID() (string, error) {
uid, ok := l.ctx.Value("user_id").(string)
@@ -150,8 +243,17 @@ func (l *UploadFileLogic) getUploadedFile(r *http.Request) (multipart.File, *mul
return file, header, nil
}
// 解析 AI 识别结果
func (l *UploadFileLogic) parseAIRecognitionResult(r *http.Request) (types.File, error) {
// 解析上传的文件
func (l *UploadFileLogic) getUploadedThumbnail(r *http.Request) (multipart.File, *multipart.FileHeader, error) {
file, header, err := r.FormFile("thumbnail")
if err != nil {
return nil, nil, errors.New("file not found")
}
return file, header, nil
}
// 解析图片信息结果
func (l *UploadFileLogic) parseImageInfoResult(r *http.Request) (types.File, error) {
formValue := r.PostFormValue("data")
var result types.File
if err := json.Unmarshal([]byte(formValue), &result); err != nil {
@@ -160,47 +262,11 @@ func (l *UploadFileLogic) parseAIRecognitionResult(r *http.Request) (types.File,
return result, nil
}
// 提取拍摄时间
func (l *UploadFileLogic) extractOriginalDateTime(exif map[string]interface{}) (string, error) {
if dateTimeOriginal, ok := exif["DateTimeOriginal"].(string); ok {
parsedTime, err := time.Parse(time.RFC3339, dateTimeOriginal)
if err == nil {
return parsedTime.Format("2006-01-02 15:04:05"), nil
}
}
return "", nil
}
// 根据 GPS 信息获取地理位置信息
func (l *UploadFileLogic) getGeoLocation(latitude, longitude float64) (string, string, string, error) {
if latitude == 0.000000 || longitude == 0.000000 {
return "", "", "", nil
}
//request := gao_map.ReGeoRequest{Location: fmt.Sprintf("%f,%f", latitude, longitude)}
//
//location, err := l.svcCtx.GaoMap.Location.ReGeo(&request)
//if err != nil {
// return nil, errors.New("regeo failed")
//}
//
//addressInfo := map[string]string{}
//if location.ReGeoCode.AddressComponent.Country != "" {
// addressInfo["country"] = location.ReGeoCode.AddressComponent.Country
//}
//if location.ReGeoCode.AddressComponent.Province != "" {
// addressInfo["province"] = location.ReGeoCode.AddressComponent.Province
//}
//if location.ReGeoCode.AddressComponent.City != "" {
// addressInfo["city"] = location.ReGeoCode.AddressComponent.City.(string)
//}
//if location.ReGeoCode.AddressComponent.District != "" {
// addressInfo["district"] = location.ReGeoCode.AddressComponent.District.(string)
//}
//if location.ReGeoCode.AddressComponent.Township != "" {
// addressInfo["township"] = location.ReGeoCode.AddressComponent.Township
//}
country, province, city, err := geo_json.GetAddress(latitude, longitude, l.svcCtx.GeoRegionData)
if err != nil {
return "", "", "", errors.New("get geo location failed")
@@ -210,20 +276,21 @@ func (l *UploadFileLogic) getGeoLocation(latitude, longitude float64) (string, s
}
// 上传文件到 OSS
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, string, string, string, error) {
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, error) {
cacheKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheKey, uid, result.Provider)
if err != nil {
return "", "", "", "", errors.New("get oss config failed")
return "", errors.New("get oss config failed")
}
service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
if err != nil {
return "", "", "", "", errors.New("get storage failed")
return "", errors.New("get storage failed")
}
objectKey := path.Join(
uid,
time.Now().Format("2006/01"), // 按年/月划分目录
l.classifyFile(result.FileType, result.IsScreenshot),
fmt.Sprintf("%s_%s%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid(), filepath.Ext(header.Filename)),
)
@@ -231,13 +298,50 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
"Content-Type": header.Header.Get("Content-Type"),
})
if err != nil {
return "", "", "", "", errors.New("upload file failed")
return "", errors.New("upload file failed")
}
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, objectKey, time.Hour*24*7)
//url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, objectKey, time.Hour*24*7)
//if err != nil {
// return "", "", errors.New("presigned url failed")
//}
return objectKey, nil
}
func (l *UploadFileLogic) uploadFileToMinio(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, string, error) {
objectKey := path.Join(
uid,
time.Now().Format("2006/01"), // 按年/月划分目录
l.classifyFile(result.FileType, result.IsScreenshot),
fmt.Sprintf("%s_%s%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid(), filepath.Ext(header.Filename)),
)
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
}
}
// 上传到MinIO
_, err = l.svcCtx.MinioClient.PutObject(
l.ctx,
constant.ThumbnailBucketName,
objectKey,
file,
int64(result.ThumbSize),
minio.PutObjectOptions{
ContentType: result.FileType,
},
)
if err != nil {
return "", "", "", "", errors.New("presigned url failed")
return "", "", err
}
return ossConfig.BucketName, ossConfig.Provider, objectKey, url, nil
reqParams := make(url.Values)
presignedURL, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, objectKey, time.Hour*24*7, reqParams)
if err != nil {
return "", "", err
}
return objectKey, presignedURL.String(), nil
}
func (l *UploadFileLogic) saveFileLocationInfoToDB(uid string, provider string, bucket string, latitude float64, longitude float64, country string, province string, city string, filePath string) (int64, error) {