⬆️ upgrade dependencies

This commit is contained in:
2025-03-14 18:15:10 +08:00
parent 7b2b6cc76b
commit 3a03224f8c
44 changed files with 960 additions and 509 deletions

View File

@@ -0,0 +1,38 @@
package auth
import (
"context"
"errors"
"schisandra-album-cloud-microservices/common/constant"
"github.com/zeromicro/go-zero/core/logx"
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
)
type LogoutLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
return &LogoutLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *LogoutLogic) Logout() (resp string, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return "", errors.New("user_id not found")
}
cacheKey := constant.UserTokenPrefix + uid
err = l.svcCtx.RedisClient.Del(l.ctx, cacheKey).Err()
if err != nil {
l.Logger.Error("logout failed")
return "", errors.New("logout failed")
}
return "logout success", nil
}

View File

@@ -67,6 +67,7 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
var queryCondition []gen.Condition
queryCondition = append(queryCondition, storageInfo.UserID.Eq(uid))
queryCondition = append(queryCondition, storageInfo.AlbumID.Eq(req.ID))
queryCondition = append(queryCondition, storageInfo.IsEncrypted.Eq(constant.NoEncrypt))
// 类型筛选 1 是分享类型
if req.Type != constant.AlbumTypeShared {
queryCondition = append(queryCondition, storageInfo.Provider.Eq(req.Provider))

View File

@@ -0,0 +1,51 @@
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 GetCoordinateListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetCoordinateListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCoordinateListLogic {
return &GetCoordinateListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetCoordinateListLogic) GetCoordinateList() (resp *types.CoordinateListResponse, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return nil, errors.New("user_id not found")
}
storageLocation := l.svcCtx.DB.ScaStorageLocation
storageInfo := l.svcCtx.DB.ScaStorageInfo
var records []types.CoordinateMeta
err = storageLocation.Select(
storageLocation.ID,
storageLocation.Longitude,
storageLocation.Latitude,
storageInfo.ID.Count().As("image_count"),
).Join(
storageInfo,
storageLocation.ID.EqCol(storageInfo.LocationID),
).Where(storageLocation.UserID.Eq(uid),
storageInfo.UserID.Eq(uid),
).Scan(&records)
if err != nil {
return nil, err
}
return &types.CoordinateListResponse{
Records: records,
}, nil
}

View File

@@ -74,7 +74,8 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.FaceID.Eq(req.FaceID)).
storageInfo.FaceID.Eq(req.FaceID),
storageInfo.IsEncrypted.Eq(constant.NoEncrypt)).
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
if err != nil {
return nil, err

View File

@@ -0,0 +1,289 @@
package storage
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/go-resty/resty/v2"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
"gorm.io/gen"
"io"
"math/rand"
"net/http"
"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"
"sort"
"sync"
"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 GetPrivateImageListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
RestyClient *resty.Client
}
func NewGetPrivateImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPrivateImageListLogic {
return &GetPrivateImageListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
RestyClient: resty.New().
SetTimeout(30 * time.Second). // 总超时时间
SetRetryCount(3). // 重试次数
SetRetryWaitTime(5 * time.Second). // 重试等待时间
SetRetryMaxWaitTime(30 * time.Second). // 最大重试等待
AddRetryCondition(func(r *resty.Response, err error) bool {
return r.StatusCode() == http.StatusTooManyRequests ||
err != nil ||
r.StatusCode() >= 500
}),
}
}
func (l *GetPrivateImageListLogic) GetPrivateImageList(req *types.PrivateImageListRequest) (resp *types.AllImageListResponse, err error) {
uid, ok := l.ctx.Value("user_id").(string)
if !ok {
return nil, errors.New("user_id not found")
}
storageInfo := l.svcCtx.DB.ScaStorageInfo
conditions := []gen.Condition{
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Neq(constant.ImageTypeShared),
storageInfo.IsDisplayed.Eq(0),
storageInfo.IsEncrypted.Eq(constant.Encrypt),
}
var storageInfoList []types.FileInfoResult
err = storageInfo.Select(
storageInfo.ID,
storageInfo.FileName,
storageInfo.CreatedAt,
storageInfo.Path).
Where(conditions...).
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
if err != nil {
return nil, err
}
if len(storageInfoList) == 0 {
return &types.AllImageListResponse{}, 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")
}
// 按日期进行分组
g, ctx := errgroup.WithContext(l.ctx)
sem := semaphore.NewWeighted(10) // 限制并发数为 10
groupedImages := sync.Map{}
for _, dbFileInfo := range storageInfoList {
dbFileInfo := dbFileInfo // 创建局部变量以避免闭包问题
if err := sem.Acquire(ctx, 1); err != nil {
logx.Error("Failed to acquire semaphore:", err)
continue
}
g.Go(func() error {
defer sem.Release(1)
// 生成单条缓存键(包含文件唯一标识)
imageCacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%v",
constant.ImageCachePrefix,
uid,
"list",
req.Provider,
req.Bucket,
dbFileInfo.ID)
// 尝试获取单条缓存
if cached, err := l.svcCtx.RedisClient.Get(l.ctx, imageCacheKey).Result(); err == nil {
var meta types.ImageMeta
if err := json.Unmarshal([]byte(cached), &meta); err == nil {
parse, err := time.Parse("2006-01-02 15:04:05", meta.CreatedAt)
if err == nil {
logx.Error("Parse Time Error:", err)
return nil
}
date := parse.Format("2006年1月2日 星期") + WeekdayMap[parse.Weekday()]
value, _ := groupedImages.LoadOrStore(date, []types.ImageMeta{})
images := value.([]types.ImageMeta)
images = append(images, meta)
groupedImages.Store(date, images)
return nil
}
}
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Minute*30)
if err != nil {
logx.Error(err)
return err
}
imageBytes, err := l.DownloadAndDecrypt(l.ctx, url, uid)
if err != nil {
logx.Error(err)
return err
}
imageData, err := l.svcCtx.XCipher.Decrypt(imageBytes, []byte(uid))
if err != nil {
logx.Error(err)
return err
}
// 使用 Load 或 Store 确保原子操作
value, _ := groupedImages.LoadOrStore(date, []types.ImageMeta{})
images := value.([]types.ImageMeta)
images = append(images, types.ImageMeta{
ID: dbFileInfo.ID,
FileName: dbFileInfo.FileName,
URL: base64.StdEncoding.EncodeToString(imageData),
Width: dbFileInfo.ThumbW,
Height: dbFileInfo.ThumbH,
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
})
// 重新存储更新后的图像列表
groupedImages.Store(date, images)
// 缓存单条数据24小时基础缓存 + 随机防雪崩)
if data, err := json.Marshal(images); err == nil {
expire := 24*time.Hour + time.Duration(rand.Intn(3600))*time.Second
if err := l.svcCtx.RedisClient.Set(l.ctx, imageCacheKey, data, expire).Err(); err != nil {
logx.Error("Failed to cache image meta:", err)
}
}
return nil
})
}
// 等待所有 goroutine 完成
if err = g.Wait(); err != nil {
return nil, err
}
var imageList []types.AllImageDetail
groupedImages.Range(func(key, value interface{}) bool {
imageList = append(imageList, types.AllImageDetail{
Date: key.(string),
List: value.([]types.ImageMeta),
})
return true
})
sort.Slice(imageList, func(i, j int) bool {
if len(imageList[i].List) == 0 || len(imageList[j].List) == 0 {
return false // 空列表不参与排序
}
createdAtI, _ := time.Parse("2006-01-02 15:04:05", imageList[i].List[0].CreatedAt)
createdAtJ, _ := time.Parse("2006-01-02 15:04:05", imageList[j].List[0].CreatedAt)
return createdAtI.After(createdAtJ) // 降序排序
})
resp = &types.AllImageListResponse{
Records: imageList,
}
return resp, nil
}
// 提取解密操作为函数
func (l *GetPrivateImageListLogic) 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 *GetPrivateImageListLogic) 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
}
func (l *GetPrivateImageListLogic) DownloadAndDecrypt(ctx context.Context, url string, uid string) ([]byte, error) {
resp, err := l.RestyClient.R().
SetContext(ctx).
SetDoNotParseResponse(true). // 保持原始响应流
Get(url)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.RawBody().Close()
if resp.StatusCode() != http.StatusOK {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode(), resp.Status())
}
// 使用缓冲区分块读取
buf := new(bytes.Buffer)
if _, err := io.Copy(buf, resp.RawBody()); err != nil {
return nil, fmt.Errorf("read response body failed: %w", err)
}
return buf.Bytes(), nil
}

View File

@@ -79,6 +79,7 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Neq(constant.ImageTypeShared),
storageInfo.IsDisplayed.Eq(0),
storageInfo.IsEncrypted.Eq(constant.NoEncrypt),
}
queryCondition = append(queryCondition, conditions...)
if req.Type != "all" {

View File

@@ -79,7 +79,8 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageLocation.ID.Eq(req.ID)).
storageLocation.ID.Eq(req.ID),
storageInfo.IsEncrypted.Eq(constant.NoEncrypt)).
Order(storageInfo.CreatedAt.Desc())
err = storageInfoQuery.Scan(&storageInfoList)
if err != nil {

View File

@@ -82,7 +82,8 @@ func (l *QueryRecentImageListLogic) QueryRecentImageList(req *types.RecentListRe
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageInfo.Type.Neq(constant.ImageTypeShared),
storageInfo.CreatedAt.Gt(thirtyDaysAgo)).
storageInfo.CreatedAt.Gt(thirtyDaysAgo),
storageInfo.IsEncrypted.Eq(constant.NoEncrypt)).
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
if err != nil {
return nil, err

View File

@@ -79,7 +79,8 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
storageInfo.UserID.Eq(uid),
storageInfo.Provider.Eq(req.Provider),
storageInfo.Bucket.Eq(req.Bucket),
storageExtra.Tag.Eq(req.TagName)).
storageExtra.Tag.Eq(req.TagName),
storageInfo.IsEncrypted.Eq(constant.NoEncrypt)).
Order(storageInfo.CreatedAt.Desc())
err = storageInfoQuery.Scan(&storageInfoList)
if err != nil {

View File

@@ -110,6 +110,15 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
return nil
})
}
var imageBytes []byte
if settingResult.Encrypt {
encryptedData, err := l.svcCtx.XCipher.Encrypt(data, []byte(uid))
if err != nil {
return "", err
}
imageBytes = encryptedData
}
// 上传文件到 OSS
g.Go(func() error {
if err := sem.Acquire(ctx, 1); err != nil {
@@ -117,16 +126,7 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
}
defer sem.Release(1)
// 重新创建 `multipart.File` 兼容的 `Reader`
fileReader := struct {
*bytes.Reader
io.Closer
}{
Reader: bytes.NewReader(data),
Closer: io.NopCloser(nil),
}
fileUrl, thumbUrl, err := l.uploadFileToOSS(uid, header, fileReader, thumbnail, result)
fileUrl, thumbUrl, err := l.uploadFileToOSS(uid, header, bytes.NewReader(imageBytes), thumbnail, result)
if err != nil {
return err
}
@@ -148,6 +148,7 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
FileSize: header.Size,
FilePath: filePath,
ThumbPath: thumbPath,
Setting: settingResult,
}
// 转换为 JSON
messageData, err := json.Marshal(fileUploadMessage)
@@ -209,7 +210,7 @@ func (l *UploadFileLogic) parseUploadSettingResult(r *http.Request) (types.Uploa
}
// 上传文件到 OSS
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, thumbnail multipart.File, result types.File) (string, string, error) {
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file io.Reader, thumbnail io.Reader, result types.File) (string, string, error) {
cacheKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheKey, uid, result.Provider)
if err != nil {