✨ added apis and optimized table structures
This commit is contained in:
@@ -593,6 +593,11 @@ type (
|
||||
SingleImageRequest {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
DeleteImageRequest {
|
||||
IDS []int64 `json:"ids"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
StorageMeta {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
@@ -605,6 +610,22 @@ type (
|
||||
StorageListResponse {
|
||||
Records []StroageNode `json:"records"`
|
||||
}
|
||||
QueryDeleteRecordRequest {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
DeleteRecordListResponse {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
BucketCapacityRequest {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
BucketCapacityResponse {
|
||||
Capacity string `json:"capacity"`
|
||||
Used string `json:"used"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
)
|
||||
|
||||
// 文件上传
|
||||
@@ -695,6 +716,18 @@ service auth {
|
||||
// 获取用户存储配置列表
|
||||
@handler getUserStorageList
|
||||
post /user/config/list returns (StorageListResponse)
|
||||
|
||||
// 删除图片
|
||||
@handler deleteImage
|
||||
post /image/delete (DeleteImageRequest) returns (string)
|
||||
|
||||
// 获取删除记录
|
||||
@handler getDeleteRecord
|
||||
post /delete/record (QueryDeleteRecordRequest) returns (DeleteRecordListResponse)
|
||||
|
||||
// 获取存储桶的容量信息
|
||||
@handler getBucketCapacity
|
||||
post /bucket/capacity (BucketCapacityRequest) returns (BucketCapacityResponse)
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -717,7 +750,7 @@ type (
|
||||
Images []ShareImageMeta `json:"images"`
|
||||
}
|
||||
QueryShareImageRequest {
|
||||
ShareCode string `json:"share_code"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
AccessPassword string `json:"access_password,omitempty"`
|
||||
}
|
||||
ShareImageListMeta {
|
||||
@@ -725,13 +758,13 @@ type (
|
||||
FileName string `json:"file_name"`
|
||||
URL string `json:"url"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
ThumbW float64 `json:"thumb_w"`
|
||||
ThumbH float64 `json:"thumb_h"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
ThumbSize float64 `json:"thumb_size"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
QueryShareImageResponse {
|
||||
List []ShareImageListMeta `json:"list"`
|
||||
Records []ShareImageListMeta `json:"records"`
|
||||
}
|
||||
ShareRecordListRequest {
|
||||
DateRange []string `json:"date_range"`
|
||||
@@ -741,7 +774,7 @@ type (
|
||||
ID int64 `json:"id"`
|
||||
CoverImage string `json:"cover_image"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ShareCode string `json:"share_code"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
VisitLimit int64 `json:"visit_limit"`
|
||||
AccessPassword string `json:"access_password"`
|
||||
ValidityPeriod int64 `json:"validity_period"`
|
||||
@@ -749,6 +782,31 @@ type (
|
||||
ShareRecordListResponse {
|
||||
records []ShareRecord `json:"records"`
|
||||
}
|
||||
QueryShareInfoRequest {
|
||||
InviteCode string `json:"invite_code"`
|
||||
}
|
||||
ShareInfoResponse {
|
||||
ID int64 `json:"id"`
|
||||
CoverImage string `json:"cover_image"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
VisitLimit int64 `json:"visit_limit"`
|
||||
ExpireTime string `json:"expire_time"`
|
||||
ImageCount int64 `json:"image_count"`
|
||||
VisitCount int64 `json:"visit_count"`
|
||||
ViewerCount int64 `json:"viewer_count"`
|
||||
SharerAvatar string `json:"sharer_avatar"`
|
||||
SharerName string `json:"sharer_name"`
|
||||
AlbumName string `json:"album_name"`
|
||||
}
|
||||
// 分享数据概览响应参数
|
||||
ShareOverviewResponse {
|
||||
VisitCount int64 `json:"visit_count"`
|
||||
VisitCountToday int64 `json:"visit_count_today"`
|
||||
ViewerCount int64 `json:"viewer_count"`
|
||||
ViewerCountToday int64 `json:"viewer_count_today"`
|
||||
PublishCount int64 `json:"publish_count"`
|
||||
PublishCountToday int64 `json:"publish_count_today"`
|
||||
}
|
||||
)
|
||||
|
||||
// 分享服务
|
||||
@@ -774,5 +832,13 @@ service auth {
|
||||
// 列出分享记录
|
||||
@handler listShareRecord
|
||||
post /record/list (ShareRecordListRequest) returns (ShareRecordListResponse)
|
||||
|
||||
// 查看分享信息
|
||||
@handler queryShareInfo
|
||||
post /info (QueryShareInfoRequest) returns (ShareInfoResponse)
|
||||
|
||||
// 查询浏览数据概览
|
||||
@handler queryShareOverview
|
||||
post /overview returns (ShareOverviewResponse)
|
||||
}
|
||||
|
||||
|
@@ -168,6 +168,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/image/list",
|
||||
Handler: share.QueryShareImageHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/info",
|
||||
Handler: share.QueryShareInfoHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/overview",
|
||||
Handler: share.QueryShareOverviewHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/record/list",
|
||||
@@ -241,11 +251,21 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/album/rename",
|
||||
Handler: storage.RenameAlbumHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/bucket/capacity",
|
||||
Handler: storage.GetBucketCapacityHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/config",
|
||||
Handler: storage.SetStorageConfigHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/delete/record",
|
||||
Handler: storage.GetDeleteRecordHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/face/detail/list",
|
||||
@@ -271,6 +291,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/image/all/list",
|
||||
Handler: storage.QueryAllImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/delete",
|
||||
Handler: storage.DeleteImageHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/location/detail/list",
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/share"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func QueryShareInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryShareInfoRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := share.NewQueryShareInfoLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryShareInfo(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/share"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func QueryShareOverviewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := share.NewQueryShareOverviewLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryShareOverview()
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func DeleteImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.DeleteImageRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewDeleteImageLogic(r.Context(), svcCtx)
|
||||
resp, err := l.DeleteImage(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func GetBucketCapacityHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.BucketCapacityRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewGetBucketCapacityLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetBucketCapacity(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func GetDeleteRecordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.QueryDeleteRecordRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewGetDeleteRecordLogic(r.Context(), svcCtx)
|
||||
resp, err := l.GetDeleteRecord(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ func (l *ListShareRecordLogic) ListShareRecord(req *types.ShareRecordListRequest
|
||||
var recordList []types.ShareRecord
|
||||
query := storageShare.
|
||||
Select(storageShare.ID,
|
||||
storageShare.ShareCode,
|
||||
storageShare.InviteCode,
|
||||
storageShare.VisitLimit,
|
||||
storageShare.AccessPassword,
|
||||
storageShare.ValidityPeriod,
|
||||
|
@@ -41,7 +41,7 @@ func (l *QueryShareImageLogic) QueryShareImage(req *types.QueryShareImageRequest
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 获取分享记录
|
||||
cacheKey := constant.ImageSharePrefix + req.ShareCode
|
||||
cacheKey := constant.ImageSharePrefix + req.InviteCode
|
||||
shareData, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
@@ -66,7 +66,7 @@ func (l *QueryShareImageLogic) QueryShareImage(req *types.QueryShareImageRequest
|
||||
|
||||
// 检查访问限制
|
||||
if storageShare.VisitLimit > 0 {
|
||||
err = l.incrementVisitCount(req.ShareCode, storageShare.VisitLimit)
|
||||
err = l.incrementVisitCount(req.InviteCode, storageShare.VisitLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (l *QueryShareImageLogic) QueryShareImage(req *types.QueryShareImageRequest
|
||||
}
|
||||
|
||||
// 生成缓存键(在验证通过后)
|
||||
resultCacheKey := constant.ImageListPrefix + req.ShareCode + ":" + req.AccessPassword
|
||||
resultCacheKey := constant.ImageCachePrefix + storageShare.UserID + ":share:" + req.InviteCode
|
||||
|
||||
// 尝试从缓存中获取结果
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, resultCacheKey).Result()
|
||||
@@ -131,7 +131,7 @@ func (l *QueryShareImageLogic) queryShareImageFromSource(storageShare *model.Sca
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize).
|
||||
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
Where(
|
||||
storageInfo.Type.Eq(constant.ImageTypeShared),
|
||||
storageInfo.AlbumID.Eq(storageShare.AlbumID)).
|
||||
@@ -175,8 +175,8 @@ func (l *QueryShareImageLogic) queryShareImageFromSource(storageShare *model.Sca
|
||||
ResultList = append(ResultList, types.ShareImageListMeta{
|
||||
ID: imgInfo.ID,
|
||||
FileName: imgInfo.FileName,
|
||||
ThumbH: imgInfo.ThumbH,
|
||||
ThumbW: imgInfo.ThumbW,
|
||||
Height: imgInfo.ThumbH,
|
||||
Width: imgInfo.ThumbW,
|
||||
ThumbSize: imgInfo.ThumbSize,
|
||||
CreatedAt: imgInfo.CreatedAt.Format(constant.TimeFormat),
|
||||
URL: ossURL,
|
||||
@@ -192,7 +192,7 @@ func (l *QueryShareImageLogic) queryShareImageFromSource(storageShare *model.Sca
|
||||
}
|
||||
|
||||
return &types.QueryShareImageResponse{
|
||||
List: ResultList}, nil
|
||||
Records: ResultList}, nil
|
||||
}
|
||||
|
||||
func (l *QueryShareImageLogic) recordUserVisit(shareID int64, userID string) error {
|
||||
|
75
app/auth/api/internal/logic/share/query_share_info_logic.go
Normal file
75
app/auth/api/internal/logic/share/query_share_info_logic.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package share
|
||||
|
||||
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 QueryShareInfoLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryShareInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryShareInfoLogic {
|
||||
return &QueryShareInfoLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryShareInfoLogic) QueryShareInfo(req *types.QueryShareInfoRequest) (resp *types.ShareInfoResponse, 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
|
||||
shareVisit := l.svcCtx.DB.ScaStorageShareVisit
|
||||
authUser := l.svcCtx.DB.ScaAuthUser
|
||||
|
||||
var shareInfo types.ShareInfoResponse
|
||||
err = storageShare.Select(
|
||||
storageShare.ID,
|
||||
storageShare.VisitLimit,
|
||||
storageShare.InviteCode,
|
||||
storageShare.ExpireTime,
|
||||
storageShare.CreatedAt,
|
||||
storageAlbum.CoverImage,
|
||||
storageAlbum.AlbumName,
|
||||
storageShare.ImageCount,
|
||||
shareVisit.Views.As("visit_count"),
|
||||
shareVisit.UserID.Count().As("viewer_count"),
|
||||
authUser.Avatar.As("sharer_avatar"),
|
||||
authUser.Nickname.As("sharer_name")).
|
||||
LeftJoin(storageAlbum, storageShare.AlbumID.EqCol(storageAlbum.ID)).
|
||||
Join(shareVisit, storageShare.ID.EqCol(shareVisit.ShareID)).
|
||||
LeftJoin(authUser, storageShare.UserID.EqCol(authUser.UID)).
|
||||
Where(
|
||||
storageShare.InviteCode.Eq(req.InviteCode),
|
||||
shareVisit.UserID.Eq(uid)).
|
||||
Group(
|
||||
storageShare.ID,
|
||||
storageShare.VisitLimit,
|
||||
storageShare.InviteCode,
|
||||
storageShare.ExpireTime,
|
||||
storageShare.CreatedAt,
|
||||
storageAlbum.CoverImage,
|
||||
storageShare.ImageCount,
|
||||
storageAlbum.AlbumName,
|
||||
shareVisit.Views,
|
||||
authUser.Avatar,
|
||||
authUser.Nickname).
|
||||
Scan(&shareInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &shareInfo, nil
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type QueryShareOverviewLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryShareOverviewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryShareOverviewLogic {
|
||||
return &QueryShareOverviewLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryShareOverviewLogic) QueryShareOverview() (resp *types.ShareOverviewResponse, 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
|
||||
shareVisit := l.svcCtx.DB.ScaStorageShareVisit
|
||||
// 统计所有数据
|
||||
var totalResult struct {
|
||||
TotalCount int64
|
||||
TotalViews int64
|
||||
TotalUsers int64
|
||||
}
|
||||
err = storageShare.Select(
|
||||
storageShare.ID.Count().As("total_count"),
|
||||
shareVisit.Views.Sum().As("total_views"),
|
||||
shareVisit.UserID.Distinct().Count().As("total_users"),
|
||||
).
|
||||
Join(shareVisit, storageShare.ID.EqCol(shareVisit.ShareID)).
|
||||
Where(storageShare.UserID.Eq(uid)).
|
||||
Scan(&totalResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 统计当天数据
|
||||
var dailyResult struct {
|
||||
DailyCount int64
|
||||
DailyViews int64
|
||||
DailyUsers int64
|
||||
}
|
||||
err = storageShare.Select(
|
||||
storageShare.ID.Count().As("daily_count"),
|
||||
shareVisit.Views.Sum().As("daily_views"),
|
||||
shareVisit.UserID.Distinct().Count().As("daily_users"),
|
||||
).
|
||||
Join(shareVisit, storageShare.ID.EqCol(shareVisit.ShareID)).
|
||||
Where(storageShare.UserID.Eq(uid),
|
||||
shareVisit.CreatedAt.Gte(time.Now().Truncate(24*time.Hour))).
|
||||
Scan(&dailyResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 合并结果到 ShareOverviewResponse
|
||||
response := types.ShareOverviewResponse{
|
||||
VisitCount: totalResult.TotalViews, // 总访问量
|
||||
VisitCountToday: dailyResult.DailyViews, // 当天访问量
|
||||
ViewerCount: totalResult.TotalUsers, // 总独立用户数
|
||||
ViewerCountToday: dailyResult.DailyUsers, // 当天独立用户数
|
||||
PublishCount: totalResult.TotalCount, // 总发布量
|
||||
PublishCountToday: dailyResult.DailyCount, // 当天发布量
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
@@ -94,12 +94,13 @@ func (l *UploadShareImageLogic) UploadShareImage(req *types.ShareImageRequest) (
|
||||
storageShare := model.ScaStorageShare{
|
||||
UserID: uid,
|
||||
AlbumID: album.ID,
|
||||
ShareCode: kgo.SimpleUuid(),
|
||||
InviteCode: kgo.SimpleUuid(),
|
||||
Status: 0,
|
||||
AccessPassword: req.AccessPassword,
|
||||
VisitLimit: req.AccessLimit,
|
||||
ValidityPeriod: int64(duration),
|
||||
ExpireTime: expiryTime,
|
||||
ImageCount: int64(len(req.Images)),
|
||||
}
|
||||
err = tx.ScaStorageShare.Create(&storageShare)
|
||||
if err != nil {
|
||||
@@ -112,7 +113,7 @@ func (l *UploadShareImageLogic) UploadShareImage(req *types.ShareImageRequest) (
|
||||
tx.Rollback()
|
||||
return "", err
|
||||
}
|
||||
cacheKey := constant.ImageSharePrefix + storageShare.ShareCode
|
||||
cacheKey := constant.ImageSharePrefix + storageShare.InviteCode
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshal, time.Duration(duration)*time.Hour*24).Err()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
@@ -124,56 +125,10 @@ func (l *UploadShareImageLogic) UploadShareImage(req *types.ShareImageRequest) (
|
||||
logx.Errorf("Transaction commit failed: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return storageShare.ShareCode, nil
|
||||
return storageShare.InviteCode, 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)
|
||||
@@ -211,24 +166,72 @@ func (l *UploadShareImageLogic) uploadImageAndRecord(tx *query.QueryTx, uid stri
|
||||
|
||||
// 记录原始图片信息
|
||||
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,
|
||||
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,
|
||||
IsDisplayed: 1,
|
||||
}
|
||||
err = tx.ScaStorageInfo.Create(&imageRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 上传缩略图到 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{
|
||||
InfoID: imageRecord.ID,
|
||||
UserID: uid,
|
||||
ThumbPath: thumbObjectKey,
|
||||
ThumbW: img.ThumbW,
|
||||
ThumbH: img.ThumbH,
|
||||
ThumbSize: float64(len(thumbnail)),
|
||||
}
|
||||
err = tx.ScaStorageThumb.Create(&thumbRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
97
app/auth/api/internal/logic/storage/delete_image_logic.go
Normal file
97
app/auth/api/internal/logic/storage/delete_image_logic.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
|
||||
"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 DeleteImageLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewDeleteImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteImageLogic {
|
||||
return &DeleteImageLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DeleteImageLogic) DeleteImage(req *types.DeleteImageRequest) (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()
|
||||
}
|
||||
}()
|
||||
storageInfo := tx.ScaStorageInfo
|
||||
info, err := storageInfo.Where(storageInfo.UserID.Eq(uid),
|
||||
storageInfo.ID.In(req.IDS...),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket)).Delete()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return "", err
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
tx.Rollback()
|
||||
return "", errors.New("no image found")
|
||||
}
|
||||
storageThumb := tx.ScaStorageThumb
|
||||
resultInfo, err := storageThumb.Where(storageThumb.UserID.Eq(uid), storageThumb.InfoID.In(req.IDS...)).Delete()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return "", err
|
||||
}
|
||||
if resultInfo.RowsAffected == 0 {
|
||||
tx.Rollback()
|
||||
return "", errors.New("no thumb found")
|
||||
}
|
||||
storageExtra := tx.ScaStorageExtra
|
||||
resultExtra, err := storageExtra.Where(storageExtra.UserID.Eq(uid), storageExtra.InfoID.In(req.IDS...)).Delete()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return "", err
|
||||
}
|
||||
if resultExtra.RowsAffected == 0 {
|
||||
tx.Rollback()
|
||||
return "", errors.New("no extra found")
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return "", err
|
||||
}
|
||||
// 删除缓存
|
||||
keyPattern := fmt.Sprintf("%s%s:%s", constant.ImageCachePrefix, uid, "*")
|
||||
// 获取所有匹配的键
|
||||
keys, err := l.svcCtx.RedisClient.Keys(l.ctx, keyPattern).Result()
|
||||
if err != nil {
|
||||
logx.Errorf("获取缓存键 %s 失败: %v", keyPattern, err)
|
||||
return "", err
|
||||
}
|
||||
// 如果没有匹配的键,直接返回
|
||||
if len(keys) == 0 {
|
||||
logx.Infof("没有找到匹配的缓存键: %s", keyPattern)
|
||||
return "", nil
|
||||
}
|
||||
// 删除所有匹配的键
|
||||
if err := l.svcCtx.RedisClient.Del(l.ctx, keys...).Err(); err != nil {
|
||||
logx.Errorf("删除缓存键 %s 失败: %v", keyPattern, err)
|
||||
return "", err
|
||||
}
|
||||
return "success", nil
|
||||
}
|
@@ -13,6 +13,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -42,7 +43,7 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 缓存获取数据 v1.0.0
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%v", constant.ImageListPrefix, uid, req.Provider, req.Bucket, req.ID)
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%v", constant.ImageCachePrefix, uid, "album", req.Provider, req.Bucket, req.ID)
|
||||
// 尝试从缓存获取
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
@@ -72,7 +73,7 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize).
|
||||
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
Where(
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
@@ -147,13 +148,19 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
|
||||
})
|
||||
return true
|
||||
})
|
||||
// 按日期排序,最新的在最上面
|
||||
sort.Slice(imageList, func(i, j int) bool {
|
||||
dateI, _ := time.Parse("2006年1月2日 星期一", imageList[i].Date)
|
||||
dateJ, _ := time.Parse("2006年1月2日 星期一", imageList[j].Date)
|
||||
return dateI.After(dateJ)
|
||||
})
|
||||
resp = &types.AlbumDetailListResponse{
|
||||
Records: imageList,
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 7*24*time.Hour - time.Duration(rand.Intn(60))*time.Minute
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
211
app/auth/api/internal/logic/storage/get_bucket_capacity_logic.go
Normal file
211
app/auth/api/internal/logic/storage/get_bucket_capacity_logic.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"math"
|
||||
"math/rand"
|
||||
"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/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetBucketCapacityLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetBucketCapacityLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBucketCapacityLogic {
|
||||
return &GetBucketCapacityLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetBucketCapacityLogic) GetBucketCapacity(req *types.BucketCapacityRequest) (resp *types.BucketCapacityResponse, err error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 设计缓存键
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s", constant.BucketCapacityCachePrefix, uid, req.Provider, req.Bucket)
|
||||
// 尝试从缓存中获取容量信息
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
logx.Errorf("get bucket capacity from cache failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果缓存存在,直接返回缓存结果
|
||||
if cachedResult != "" {
|
||||
// 如果是空值缓存(防缓存穿透),返回空结果
|
||||
if cachedResult == "{}" {
|
||||
return &types.BucketCapacityResponse{}, nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(cachedResult), &resp)
|
||||
if err != nil {
|
||||
return nil, errors.New("unmarshal cached result failed")
|
||||
}
|
||||
return resp, 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")
|
||||
}
|
||||
bucketStat, err := service.GetBucketStat(l.ctx, ossConfig.BucketName)
|
||||
if err != nil {
|
||||
// 如果 OSS 接口调用失败,设置空值缓存(防缓存穿透)
|
||||
emptyData := "{}"
|
||||
emptyCacheExpire := 5 * time.Minute // 空值缓存过期时间
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, emptyData, emptyCacheExpire).Err(); err != nil {
|
||||
logx.Errorf("set empty cache failed: %v", err)
|
||||
}
|
||||
return nil, errors.New("get bucket stat failed")
|
||||
}
|
||||
scaStorageConfig := l.svcCtx.DB.ScaStorageConfig
|
||||
capacity, err := scaStorageConfig.Select(scaStorageConfig.Capacity).
|
||||
Where(scaStorageConfig.UserID.Eq(uid), scaStorageConfig.Provider.Eq(req.Provider), scaStorageConfig.Bucket.Eq(req.Bucket)).First()
|
||||
if err != nil {
|
||||
return nil, errors.New("get storage config failed")
|
||||
}
|
||||
|
||||
// 总容量(单位:GB)
|
||||
totalCapacityGB := capacity.Capacity
|
||||
|
||||
// 已用容量(单位:字节转换为 GB)
|
||||
const bytesToGB = 1024 * 1024 * 1024
|
||||
usedCapacityGB := float64(bucketStat.StandardStorage) / bytesToGB
|
||||
|
||||
// 计算百分比
|
||||
percentage := calculatePercentage(usedCapacityGB, float64(totalCapacityGB))
|
||||
|
||||
// 格式化容量信息
|
||||
capacityStr := fmt.Sprintf("%.2v GB", totalCapacityGB) // 总容量(GB)
|
||||
|
||||
resp = &types.BucketCapacityResponse{
|
||||
Capacity: capacityStr,
|
||||
Used: formatBytes(bucketStat.StandardStorage),
|
||||
Percentage: percentage,
|
||||
}
|
||||
// 缓存容量信息
|
||||
marshalData, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
return nil, errors.New("marshal bucket capacity failed")
|
||||
}
|
||||
// 添加随机值(防缓存雪崩)
|
||||
// 计算缓存过期时间:距离第二天凌晨 12 点的剩余时间
|
||||
cacheExpire := timeUntilNextMidnight() + time.Duration(rand.Intn(300))*time.Second
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshalData, cacheExpire).Err()
|
||||
if err != nil {
|
||||
return nil, errors.New("set bucket capacity failed")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *GetBucketCapacityLogic) 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 *GetBucketCapacityLogic) 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
|
||||
}
|
||||
|
||||
// 格式化字节大小为更友好的单位(KB、MB、GB 等)
|
||||
func formatBytes(bytes int64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%d B", bytes)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
// 计算使用量百分比(基于 GB)
|
||||
func calculatePercentage(usedGB, totalGB float64) float64 {
|
||||
if totalGB == 0 {
|
||||
return 0
|
||||
}
|
||||
return math.Round(usedGB/totalGB*100*100) / 100
|
||||
}
|
||||
|
||||
// 计算距离第二天凌晨 12 点的剩余时间
|
||||
func timeUntilNextMidnight() time.Duration {
|
||||
now := time.Now()
|
||||
nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
|
||||
return nextMidnight.Sub(now)
|
||||
}
|
231
app/auth/api/internal/logic/storage/get_delete_record_logic.go
Normal file
231
app/auth/api/internal/logic/storage/get_delete_record_logic.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"math/rand"
|
||||
"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"
|
||||
"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 GetDeleteRecordLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetDeleteRecordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetDeleteRecordLogic {
|
||||
return &GetDeleteRecordLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetDeleteRecordLogic) GetDeleteRecord(req *types.QueryDeleteRecordRequest) (resp *types.DeleteRecordListResponse, err error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 缓存获取数据 v1.0.0
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%s", constant.ImageCachePrefix, uid, "deleted", req.Provider, req.Bucket)
|
||||
// 尝试从缓存获取
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
var cachedResponse types.DeleteRecordListResponse
|
||||
if err := json.Unmarshal([]byte(cachedResult), &cachedResponse); err == nil {
|
||||
return &cachedResponse, nil
|
||||
}
|
||||
logx.Error("Failed to unmarshal cached image list:", err)
|
||||
return nil, errors.New("get cached image list failed")
|
||||
} else if !errors.Is(err, redis.Nil) {
|
||||
logx.Error("Redis error:", err)
|
||||
return nil, errors.New("get cached image list failed")
|
||||
}
|
||||
// 缓存未命中,从数据库中查询
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageThumb := l.svcCtx.DB.ScaStorageThumb
|
||||
var storageInfoList []types.FileInfoResult
|
||||
err = storageInfo.Select(
|
||||
storageInfo.ID,
|
||||
storageInfo.FileName,
|
||||
storageInfo.CreatedAt,
|
||||
storageThumb.ThumbPath,
|
||||
storageInfo.Path,
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
Unscoped().
|
||||
Where(
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
storageInfo.DeletedAt.IsNotNull(),
|
||||
storageInfo.Type.Neq(constant.ImageTypeShared)).
|
||||
Order(storageInfo.CreatedAt.Desc()).
|
||||
Scan(&storageInfoList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(storageInfoList) == 0 {
|
||||
return &types.DeleteRecordListResponse{}, 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")
|
||||
}
|
||||
|
||||
// 按日期进行分组
|
||||
var wg sync.WaitGroup
|
||||
groupedImages := sync.Map{}
|
||||
|
||||
for _, dbFileInfo := range storageInfoList {
|
||||
wg.Add(1)
|
||||
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)
|
||||
|
||||
images = append(images, types.ImageMeta{
|
||||
ID: dbFileInfo.ID,
|
||||
FileName: dbFileInfo.FileName,
|
||||
Thumbnail: presignedUrl.String(),
|
||||
URL: url,
|
||||
Width: dbFileInfo.ThumbW,
|
||||
Height: dbFileInfo.ThumbH,
|
||||
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
|
||||
// 重新存储更新后的图像列表
|
||||
groupedImages.Store(date, images)
|
||||
}(&dbFileInfo)
|
||||
}
|
||||
wg.Wait()
|
||||
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 {
|
||||
dateI, _ := time.Parse("2006年1月2日 星期一", imageList[i].Date)
|
||||
dateJ, _ := time.Parse("2006年1月2日 星期一", imageList[j].Date)
|
||||
return dateI.After(dateJ)
|
||||
})
|
||||
resp = &types.DeleteRecordListResponse{
|
||||
Records: imageList,
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
} else {
|
||||
logx.Error("Failed to marshal image list for caching:", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *GetDeleteRecordLogic) 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 *GetDeleteRecordLogic) 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
|
||||
}
|
@@ -12,6 +12,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -41,7 +42,7 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 缓存获取数据 v1.0.0
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%v", constant.ImageFaceListPrefix, uid, req.Provider, req.Bucket, req.FaceID)
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%v", constant.ImageCachePrefix, uid, "faces", req.Provider, req.Bucket, req.FaceID)
|
||||
// 尝试从缓存获取
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
@@ -70,7 +71,7 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize).
|
||||
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).Where(
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).Where(
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
@@ -144,13 +145,19 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
|
||||
})
|
||||
return true
|
||||
})
|
||||
// 按日期排序,最新的在最上面
|
||||
sort.Slice(imageList, func(i, j int) bool {
|
||||
dateI, _ := time.Parse("2006年1月2日 星期一", imageList[i].Date)
|
||||
dateJ, _ := time.Parse("2006年1月2日 星期一", imageList[j].Date)
|
||||
return dateI.After(dateJ)
|
||||
})
|
||||
resp = &types.FaceDetailListResponse{
|
||||
Records: imageList,
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 7*24*time.Hour - time.Duration(rand.Intn(60))*time.Minute
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -75,12 +75,12 @@ func (l *GetImageUrlLogic) GetImageUrl(req *types.SingleImageRequest) (resp stri
|
||||
if err != nil {
|
||||
return "", errors.New("get storage failed")
|
||||
}
|
||||
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, result.Path, 7*24*time.Hour)
|
||||
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, result.Path, 15*time.Minute)
|
||||
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()
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, url, 15*time.Minute).Err()
|
||||
if err != nil {
|
||||
logx.Info(err)
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"gorm.io/gen"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -49,7 +51,7 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 缓存获取数据 v1.0.0
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%t", constant.ImageListPrefix, uid, req.Provider, req.Bucket, req.Sort)
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%s:%t", constant.ImageCachePrefix, uid, "list", req.Provider, req.Bucket, req.Type, req.Sort)
|
||||
// 尝试从缓存获取
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
@@ -63,47 +65,36 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
|
||||
logx.Error("Redis error:", err)
|
||||
return nil, errors.New("get cached image list failed")
|
||||
}
|
||||
|
||||
// 缓存未命中,从数据库中查询
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageThumb := l.svcCtx.DB.ScaStorageThumb
|
||||
var storageInfoList []types.FileInfoResult
|
||||
if req.Sort {
|
||||
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 {
|
||||
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)
|
||||
var queryCondition []gen.Condition
|
||||
conditions := []gen.Condition{
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
storageInfo.Type.Neq(constant.ImageTypeShared),
|
||||
}
|
||||
queryCondition = append(queryCondition, conditions...)
|
||||
if req.Type != "all" {
|
||||
queryCondition = append(queryCondition, storageInfo.Type.Eq(req.Type))
|
||||
}
|
||||
if req.Sort {
|
||||
queryCondition = append(queryCondition, storageInfo.AlbumID.Eq(0))
|
||||
}
|
||||
var storageInfoList []types.FileInfoResult
|
||||
err = storageInfo.Select(
|
||||
storageInfo.ID,
|
||||
storageInfo.FileName,
|
||||
storageInfo.CreatedAt,
|
||||
storageThumb.ThumbPath,
|
||||
storageInfo.Path,
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
Where(queryCondition...).
|
||||
Order(storageInfo.CreatedAt.Desc()).Scan(&storageInfoList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -171,13 +162,19 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
|
||||
})
|
||||
return true
|
||||
})
|
||||
// 按日期排序,最新的在最上面
|
||||
sort.Slice(imageList, func(i, j int) bool {
|
||||
dateI, _ := time.Parse("2006年1月2日 星期一", imageList[i].Date)
|
||||
dateJ, _ := time.Parse("2006年1月2日 星期一", imageList[j].Date)
|
||||
return dateI.After(dateJ)
|
||||
})
|
||||
resp = &types.AllImageListResponse{
|
||||
Records: imageList,
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 7*24*time.Hour - time.Duration(rand.Intn(60))*time.Minute
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -42,7 +43,7 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 缓存获取数据 v1.0.0
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%v", constant.ImageListPrefix, uid, req.Provider, req.Bucket, req.ID)
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%v", constant.ImageCachePrefix, uid, "location", req.Provider, req.Bucket, req.ID)
|
||||
// 尝试从缓存获取
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
@@ -59,6 +60,7 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
|
||||
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageThumb := l.svcCtx.DB.ScaStorageThumb
|
||||
storageLocation := l.svcCtx.DB.ScaStorageLocation
|
||||
// 数据库查询文件信息列表
|
||||
var storageInfoQuery query.IScaStorageInfoDo
|
||||
var storageInfoList []types.FileInfoResult
|
||||
@@ -72,11 +74,13 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
|
||||
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)).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
LeftJoin(storageLocation, storageInfo.LocationID.EqCol(storageLocation.ID)).
|
||||
Where(
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
storageLocation.ID.Eq(req.ID)).
|
||||
Order(storageInfo.CreatedAt.Desc())
|
||||
err = storageInfoQuery.Scan(&storageInfoList)
|
||||
if err != nil {
|
||||
@@ -146,13 +150,19 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
|
||||
})
|
||||
return true
|
||||
})
|
||||
// 按日期排序,最新的在最上面
|
||||
sort.Slice(imageList, func(i, j int) bool {
|
||||
dateI, _ := time.Parse("2006年1月2日 星期一", imageList[i].Date)
|
||||
dateJ, _ := time.Parse("2006年1月2日 星期一", imageList[j].Date)
|
||||
return dateI.After(dateJ)
|
||||
})
|
||||
resp = &types.LocationDetailListResponse{
|
||||
Records: imageList,
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 7*24*time.Hour - time.Duration(rand.Intn(60))*time.Minute
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -2,17 +2,12 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"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"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -38,32 +33,27 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
storageLocation := l.svcCtx.DB.ScaStorageLocation
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
|
||||
locations, err := storageLocation.Select(
|
||||
var locations []types.LocationInfo
|
||||
err = storageLocation.Select(
|
||||
storageLocation.ID,
|
||||
storageLocation.Country,
|
||||
storageLocation.City,
|
||||
storageLocation.Province,
|
||||
storageLocation.CoverImage,
|
||||
storageLocation.Total).Where(storageLocation.UserID.Eq(uid),
|
||||
storageLocation.Provider.Eq(req.Provider),
|
||||
storageLocation.Bucket.Eq(req.Bucket)).
|
||||
Order(storageLocation.CreatedAt.Desc()).Find()
|
||||
storageInfo.ID.Count().As("total")).
|
||||
LeftJoin(storageInfo, storageInfo.LocationID.EqCol(storageLocation.ID)).
|
||||
Where(storageLocation.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket)).
|
||||
Order(storageLocation.CreatedAt.Desc()).
|
||||
Group(storageLocation.ID).
|
||||
Scan(&locations)
|
||||
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")
|
||||
//}
|
||||
locationMap := make(map[string][]types.LocationMeta)
|
||||
|
||||
for _, loc := range locations {
|
||||
@@ -79,7 +69,7 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
|
||||
city = loc.Country
|
||||
}
|
||||
reqParams := make(url.Values)
|
||||
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, loc.CoverImage, 7*24*time.Hour, reqParams)
|
||||
presignedUrl, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, loc.CoverImage, 15*time.Minute, reqParams)
|
||||
if err != nil {
|
||||
return nil, errors.New("get presigned url failed")
|
||||
}
|
||||
@@ -103,63 +93,3 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
|
||||
|
||||
return &types.LocationListResponse{Records: locationListData}, nil
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *QueryLocationImageListLogic) 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 *QueryLocationImageListLogic) 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
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -42,7 +43,7 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
// 缓存获取数据 v1.0.0
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%v", constant.ImageListPrefix, uid, req.Provider, req.Bucket, req.TagName)
|
||||
cacheKey := fmt.Sprintf("%s%s:%s:%s:%s:%v", constant.ImageCachePrefix, uid, "thing", req.Provider, req.Bucket, req.TagName)
|
||||
// 尝试从缓存获取
|
||||
cachedResult, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
@@ -59,6 +60,7 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
|
||||
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageThumb := l.svcCtx.DB.ScaStorageThumb
|
||||
storageExtra := l.svcCtx.DB.ScaStorageExtra
|
||||
// 数据库查询文件信息列表
|
||||
var storageInfoQuery query.IScaStorageInfoDo
|
||||
var storageInfoList []types.FileInfoResult
|
||||
@@ -72,12 +74,13 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
|
||||
storageThumb.ThumbW,
|
||||
storageThumb.ThumbH,
|
||||
storageThumb.ThumbSize).
|
||||
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
LeftJoin(storageExtra, storageInfo.ID.EqCol(storageExtra.InfoID)).
|
||||
Where(
|
||||
storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
storageInfo.Tag.Eq(req.TagName)).
|
||||
storageExtra.Tag.Eq(req.TagName)).
|
||||
Order(storageInfo.CreatedAt.Desc())
|
||||
err = storageInfoQuery.Scan(&storageInfoList)
|
||||
if err != nil {
|
||||
@@ -147,13 +150,19 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
|
||||
})
|
||||
return true
|
||||
})
|
||||
// 按日期排序,最新的在最上面
|
||||
sort.Slice(imageList, func(i, j int) bool {
|
||||
dateI, _ := time.Parse("2006年1月2日 星期一", imageList[i].Date)
|
||||
dateJ, _ := time.Parse("2006年1月2日 星期一", imageList[j].Date)
|
||||
return dateI.After(dateJ)
|
||||
})
|
||||
resp = &types.ThingDetailListResponse{
|
||||
Records: imageList,
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 7*24*time.Hour - time.Duration(rand.Intn(60))*time.Minute
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -40,21 +40,24 @@ func (l *QueryThingImageListLogic) QueryThingImageList(req *types.ThingListReque
|
||||
}
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageThumb := l.svcCtx.DB.ScaStorageThumb
|
||||
storageExtra := l.svcCtx.DB.ScaStorageExtra
|
||||
|
||||
var thingList []types.ThingImageList
|
||||
err = storageInfo.Select(
|
||||
storageInfo.ID,
|
||||
storageInfo.Category,
|
||||
storageInfo.Tag,
|
||||
storageExtra.Category,
|
||||
storageExtra.Tag,
|
||||
storageThumb.ThumbPath,
|
||||
storageInfo.CreatedAt).
|
||||
LeftJoin(storageThumb, storageInfo.ThumbID.EqCol(storageThumb.ID)).
|
||||
LeftJoin(storageThumb, storageInfo.ID.EqCol(storageThumb.InfoID)).
|
||||
LeftJoin(storageExtra, storageInfo.ID.EqCol(storageExtra.InfoID)).
|
||||
Where(storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
storageInfo.Category.IsNotNull(),
|
||||
storageInfo.Tag.IsNotNull(),
|
||||
storageInfo.Category.Length().Gt(0),
|
||||
storageInfo.Tag.Length().Gte(0)).
|
||||
storageExtra.Category.IsNotNull(),
|
||||
storageExtra.Tag.IsNotNull(),
|
||||
storageExtra.Category.Length().Gt(0),
|
||||
storageExtra.Tag.Length().Gte(0)).
|
||||
Order(storageInfo.CreatedAt.Desc()).
|
||||
Scan(&thingList)
|
||||
if err != nil {
|
||||
|
@@ -3,7 +3,6 @@ package storage
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"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"
|
||||
@@ -26,9 +24,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"schisandra-album-cloud-microservices/common/encrypt"
|
||||
"schisandra-album-cloud-microservices/common/geo_json"
|
||||
"schisandra-album-cloud-microservices/common/storage/config"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -172,49 +168,9 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
if err != nil {
|
||||
return "", errors.New("publish message failed")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
//// 根据 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)
|
||||
@@ -224,16 +180,6 @@ func (l *UploadFileLogic) getUserID() (string, error) {
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// 在UploadImageLogic或其他需要使缓存失效的逻辑中添加:
|
||||
func (l *UploadFileLogic) afterImageUpload(uid, provider, bucket string) {
|
||||
for _, sort := range []bool{true, false} {
|
||||
key := fmt.Sprintf("%s%s:%s:%s:%v", constant.ImageListPrefix, uid, provider, bucket, sort)
|
||||
if err := l.svcCtx.RedisClient.Del(l.ctx, key).Err(); err != nil {
|
||||
logx.Errorf("删除缓存键 %s 失败: %v", key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析上传的文件
|
||||
func (l *UploadFileLogic) getUploadedFile(r *http.Request) (multipart.File, *multipart.FileHeader, error) {
|
||||
file, header, err := r.FormFile("file")
|
||||
@@ -262,19 +208,6 @@ func (l *UploadFileLogic) parseImageInfoResult(r *http.Request) (types.File, err
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 根据 GPS 信息获取地理位置信息
|
||||
func (l *UploadFileLogic) getGeoLocation(latitude, longitude float64) (string, string, string, error) {
|
||||
if latitude == 0.000000 || longitude == 0.000000 {
|
||||
return "", "", "", nil
|
||||
}
|
||||
country, province, city, err := geo_json.GetAddress(latitude, longitude, l.svcCtx.GeoRegionData)
|
||||
if err != nil {
|
||||
return "", "", "", errors.New("get geo location failed")
|
||||
}
|
||||
|
||||
return country, province, city, nil
|
||||
}
|
||||
|
||||
// 上传文件到 OSS
|
||||
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, error) {
|
||||
cacheKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
|
||||
@@ -345,75 +278,6 @@ func (l *UploadFileLogic) uploadFileToMinio(uid string, header *multipart.FileHe
|
||||
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) {
|
||||
if latitude == 0.000000 || longitude == 0.000000 {
|
||||
return 0, nil
|
||||
}
|
||||
locationDB := l.svcCtx.DB.ScaStorageLocation
|
||||
storageLocations, err := locationDB.Where(locationDB.UserID.Eq(uid), locationDB.Province.Eq(province), locationDB.City.Eq(city)).First()
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, err
|
||||
}
|
||||
if storageLocations == nil {
|
||||
locationInfo := model.ScaStorageLocation{
|
||||
Provider: provider,
|
||||
Bucket: bucket,
|
||||
UserID: uid,
|
||||
Country: country,
|
||||
City: city,
|
||||
Province: province,
|
||||
Latitude: fmt.Sprintf("%f", latitude),
|
||||
Longitude: fmt.Sprintf("%f", longitude),
|
||||
Total: 1,
|
||||
CoverImage: filePath,
|
||||
}
|
||||
err = locationDB.Create(&locationInfo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return locationInfo.ID, nil
|
||||
} else {
|
||||
info, err := locationDB.Where(locationDB.ID.Eq(storageLocations.ID), locationDB.UserID.Eq(uid)).UpdateColumnSimple(locationDB.Total.Add(1), locationDB.CoverImage.Value(filePath))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
return 0, errors.New("update location failed")
|
||||
}
|
||||
return storageLocations.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 将 EXIF 和文件信息存入数据库
|
||||
func (l *UploadFileLogic) saveFileInfoToDB(uid, bucket, provider string, header *multipart.FileHeader, result types.File, locationId, faceId int64, filePath string) (int64, error) {
|
||||
|
||||
typeName := l.classifyFile(result.FileType, result.IsScreenshot)
|
||||
scaStorageInfo := &model.ScaStorageInfo{
|
||||
UserID: uid,
|
||||
Provider: provider,
|
||||
Bucket: bucket,
|
||||
FileName: header.Filename,
|
||||
FileSize: strconv.FormatInt(header.Size, 10),
|
||||
FileType: result.FileType,
|
||||
Path: filePath,
|
||||
Landscape: result.Landscape,
|
||||
Tag: result.TagName,
|
||||
IsAnime: strconv.FormatBool(result.IsAnime),
|
||||
Category: result.TopCategory,
|
||||
LocationID: locationId,
|
||||
FaceID: faceId,
|
||||
Type: typeName,
|
||||
Width: result.Width,
|
||||
Height: result.Height,
|
||||
}
|
||||
|
||||
err := l.svcCtx.DB.ScaStorageInfo.Create(scaStorageInfo)
|
||||
if err != nil {
|
||||
return 0, errors.New("create storage info failed")
|
||||
}
|
||||
return scaStorageInfo.ID, nil
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *UploadFileLogic) decryptConfig(dbConfig *model.ScaStorageConfig) (*config.StorageConfig, error) {
|
||||
accessKey, err := encrypt.Decrypt(dbConfig.AccessKey, l.svcCtx.Config.Encrypt.Key)
|
||||
@@ -503,28 +367,3 @@ func (l *UploadFileLogic) classifyFile(mimeType string, isScreenshot bool) strin
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// 保存最近7天上传的文件列表
|
||||
func (l *UploadFileLogic) saveRecentFileList(uid, url string, id int64, result types.File, filename string) error {
|
||||
|
||||
redisKey := constant.ImageRecentPrefix + uid + ":" + strconv.FormatInt(id, 10)
|
||||
imageMeta := types.ImageMeta{
|
||||
ID: id,
|
||||
URL: url,
|
||||
FileName: filename,
|
||||
Width: result.Width,
|
||||
Height: result.Height,
|
||||
CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
marshal, err := json.Marshal(imageMeta)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return errors.New("marshal image meta failed")
|
||||
}
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, redisKey, marshal, time.Hour*24*7).Err()
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return errors.New("save recent file list failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -55,23 +55,27 @@ func (c *NsqImageProcessConsumer) HandleMessage(msg *nsq.Message) error {
|
||||
return err
|
||||
}
|
||||
// 将地址信息保存到数据库
|
||||
locationId, err := c.saveFileLocationInfoToDB(message.UID, message.Data.Provider, message.Data.Bucket, message.Data.Latitude, message.Data.Longitude, country, province, city, message.ThumbPath)
|
||||
locationId, err := c.saveFileLocationInfoToDB(message.UID, message.Data.Latitude, message.Data.Longitude, country, province, city, message.ThumbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
thumbnailId, err := c.saveFileThumbnailInfoToDB(message.UID, message.ThumbPath, message.Data.ThumbW, message.Data.ThumbH, message.Data.ThumbSize)
|
||||
|
||||
// 将文件信息存入数据库
|
||||
id, err := c.saveFileInfoToDB(message.UID, message.Data.Bucket, message.Data.Provider, message.FileHeader, message.Data, locationId, message.FaceID, message.FilePath, thumbnailId)
|
||||
storageId, err := c.saveFileInfoToDB(message.UID, message.Data.Bucket, message.Data.Provider, message.FileHeader, message.Data, message.FaceID, message.FilePath, locationId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.saveFileThumbnailInfoToDB(message.UID, message.ThumbPath, message.Data.ThumbW, message.Data.ThumbH, message.Data.ThumbSize, storageId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
c.afterImageUpload(message.UID, message.Data.Provider, message.Data.Bucket)
|
||||
c.afterImageUpload(message.UID)
|
||||
|
||||
// redis 保存最近7天上传的文件列表
|
||||
err = c.saveRecentFileList(message.UID, message.PresignedURL, id, message.Data, message.FileHeader.Filename)
|
||||
err = c.saveRecentFileList(message.UID, message.PresignedURL, storageId, message.Data, message.FileHeader.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,15 +154,16 @@ func (c *NsqImageProcessConsumer) classifyFile(mimeType string, isScreenshot boo
|
||||
"video/x-matroska": "video",
|
||||
}
|
||||
|
||||
// 如果isScreenshot为true,则返回"screenshot"
|
||||
if isScreenshot {
|
||||
return "screenshot"
|
||||
}
|
||||
|
||||
// 根据MIME类型从map中获取分类
|
||||
if classification, exists := typeMap[mimeType]; exists {
|
||||
return classification
|
||||
}
|
||||
|
||||
// 如果isScreenshot为true,则返回"screenshot"
|
||||
if isScreenshot {
|
||||
return "screenshot"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@@ -207,7 +212,7 @@ func (c *NsqImageProcessConsumer) decryptConfig(dbConfig *model.ScaStorageConfig
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *NsqImageProcessConsumer) saveFileLocationInfoToDB(uid string, provider string, bucket string, latitude float64, longitude float64, country string, province string, city string, filePath string) (int64, error) {
|
||||
func (c *NsqImageProcessConsumer) saveFileLocationInfoToDB(uid string, latitude float64, longitude float64, country string, province string, city string, filePath string) (int64, error) {
|
||||
if latitude == 0.000000 || longitude == 0.000000 {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -218,35 +223,24 @@ func (c *NsqImageProcessConsumer) saveFileLocationInfoToDB(uid string, provider
|
||||
}
|
||||
if storageLocations == nil {
|
||||
locationInfo := model.ScaStorageLocation{
|
||||
Provider: provider,
|
||||
Bucket: bucket,
|
||||
UserID: uid,
|
||||
Country: country,
|
||||
City: city,
|
||||
Province: province,
|
||||
Latitude: fmt.Sprintf("%f", latitude),
|
||||
Longitude: fmt.Sprintf("%f", longitude),
|
||||
Total: 1,
|
||||
CoverImage: filePath,
|
||||
}
|
||||
err = locationDB.Create(&locationInfo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return locationInfo.ID, nil
|
||||
} else {
|
||||
info, err := locationDB.Where(locationDB.ID.Eq(storageLocations.ID), locationDB.UserID.Eq(uid)).UpdateColumnSimple(locationDB.Total.Add(1), locationDB.CoverImage.Value(filePath))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
return 0, errors.New("update location failed")
|
||||
}
|
||||
return storageLocations.ID, nil
|
||||
return 0, nil
|
||||
}
|
||||
return storageLocations.ID, nil
|
||||
}
|
||||
|
||||
func (c *NsqImageProcessConsumer) saveFileThumbnailInfoToDB(uid string, filePath string, width, height float64, size float64) (int64, error) {
|
||||
func (c *NsqImageProcessConsumer) saveFileThumbnailInfoToDB(uid string, filePath string, width, height float64, size float64, storageId int64) error {
|
||||
storageThumb := c.svcCtx.DB.ScaStorageThumb
|
||||
storageThumbInfo := &model.ScaStorageThumb{
|
||||
UserID: uid,
|
||||
@@ -254,18 +248,25 @@ func (c *NsqImageProcessConsumer) saveFileThumbnailInfoToDB(uid string, filePath
|
||||
ThumbW: width,
|
||||
ThumbH: height,
|
||||
ThumbSize: size,
|
||||
InfoID: storageId,
|
||||
}
|
||||
err := storageThumb.Create(storageThumbInfo)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return 0, errors.New("create storage thumb failed")
|
||||
return errors.New("create storage thumb failed")
|
||||
}
|
||||
return storageThumbInfo.ID, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将 EXIF 和文件信息存入数据库
|
||||
func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string, header *multipart.FileHeader, result types.File, locationId, faceId int64, filePath string, thumbnailId int64) (int64, error) {
|
||||
|
||||
func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string, header *multipart.FileHeader, result types.File, faceId int64, filePath string, locationID int64) (int64, error) {
|
||||
tx := c.svcCtx.DB.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback() // 如果有panic发生,回滚事务
|
||||
logx.Errorf("transaction rollback: %v", r)
|
||||
}
|
||||
}()
|
||||
typeName := c.classifyFile(result.FileType, result.IsScreenshot)
|
||||
scaStorageInfo := &model.ScaStorageInfo{
|
||||
UserID: uid,
|
||||
@@ -275,31 +276,55 @@ func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string,
|
||||
FileSize: strconv.FormatInt(header.Size, 10),
|
||||
FileType: result.FileType,
|
||||
Path: filePath,
|
||||
Landscape: result.Landscape,
|
||||
Tag: result.TagName,
|
||||
IsAnime: strconv.FormatBool(result.IsAnime),
|
||||
Category: result.TopCategory,
|
||||
LocationID: locationId,
|
||||
FaceID: faceId,
|
||||
Type: typeName,
|
||||
Width: result.Width,
|
||||
Height: result.Height,
|
||||
ThumbID: thumbnailId,
|
||||
LocationID: locationID,
|
||||
}
|
||||
|
||||
err := c.svcCtx.DB.ScaStorageInfo.Create(scaStorageInfo)
|
||||
err := tx.ScaStorageInfo.Create(scaStorageInfo)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return 0, errors.New("create storage info failed")
|
||||
}
|
||||
scaStorageExtra := &model.ScaStorageExtra{
|
||||
UserID: uid,
|
||||
InfoID: scaStorageInfo.ID,
|
||||
Landscape: result.Landscape,
|
||||
Tag: result.TagName,
|
||||
IsAnime: strconv.FormatBool(result.IsAnime),
|
||||
Category: result.TopCategory,
|
||||
}
|
||||
err = tx.ScaStorageExtra.Create(scaStorageExtra)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return 0, errors.New("create storage extra failed")
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return 0, errors.New("commit failed")
|
||||
}
|
||||
return scaStorageInfo.ID, nil
|
||||
}
|
||||
|
||||
// 在UploadImageLogic或其他需要使缓存失效的逻辑中添加:
|
||||
func (c *NsqImageProcessConsumer) afterImageUpload(uid, provider, bucket string) {
|
||||
for _, sort := range []bool{true, false} {
|
||||
key := fmt.Sprintf("%s%s:%s:%s:%v", constant.ImageListPrefix, uid, provider, bucket, sort)
|
||||
if err := c.svcCtx.RedisClient.Del(c.ctx, key).Err(); err != nil {
|
||||
logx.Errorf("删除缓存键 %s 失败: %v", key, err)
|
||||
}
|
||||
func (c *NsqImageProcessConsumer) afterImageUpload(uid string) {
|
||||
// 删除缓存
|
||||
keyPattern := fmt.Sprintf("%s%s:%s", constant.ImageCachePrefix, uid, "*")
|
||||
// 获取所有匹配的键
|
||||
keys, err := c.svcCtx.RedisClient.Keys(c.ctx, keyPattern).Result()
|
||||
if err != nil {
|
||||
logx.Errorf("获取缓存键 %s 失败: %v", keyPattern, err)
|
||||
}
|
||||
// 如果没有匹配的键,直接返回
|
||||
if len(keys) == 0 {
|
||||
logx.Infof("没有找到匹配的缓存键: %s", keyPattern)
|
||||
return
|
||||
}
|
||||
// 删除所有匹配的键
|
||||
if err := c.svcCtx.RedisClient.Del(c.ctx, keys...).Err(); err != nil {
|
||||
logx.Errorf("删除缓存键 %s 失败: %v", keyPattern, err)
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -63,3 +63,12 @@ type ShareImageInfo struct {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type LocationInfo struct {
|
||||
ID int64 `json:"id"`
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
Province string `json:"province"`
|
||||
CoverImage string `json:"cover_image"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
@@ -76,6 +76,17 @@ type AllImageListResponse struct {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
|
||||
type BucketCapacityRequest struct {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type BucketCapacityResponse struct {
|
||||
Capacity string `json:"capacity"`
|
||||
Used string `json:"used"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
type CommentContent struct {
|
||||
NickName string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
@@ -147,6 +158,16 @@ type CommentResponse struct {
|
||||
ReplyTo int64 `json:"reply_to,omitempty"`
|
||||
}
|
||||
|
||||
type DeleteImageRequest struct {
|
||||
IDS []int64 `json:"ids"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type DeleteRecordListResponse struct {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
|
||||
type FaceDetailListRequest struct {
|
||||
FaceID int64 `json:"face_id"`
|
||||
Provider string `json:"provider"`
|
||||
@@ -254,13 +275,22 @@ type PhoneLoginRequest struct {
|
||||
AutoLogin bool `json:"auto_login"`
|
||||
}
|
||||
|
||||
type QueryDeleteRecordRequest struct {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type QueryShareImageRequest struct {
|
||||
ShareCode string `json:"share_code"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
AccessPassword string `json:"access_password,omitempty"`
|
||||
}
|
||||
|
||||
type QueryShareImageResponse struct {
|
||||
List []ShareImageListMeta `json:"list"`
|
||||
Records []ShareImageListMeta `json:"records"`
|
||||
}
|
||||
|
||||
type QueryShareInfoRequest struct {
|
||||
InviteCode string `json:"invite_code"`
|
||||
}
|
||||
|
||||
type RecentListResponse struct {
|
||||
@@ -320,8 +350,8 @@ type ShareImageListMeta struct {
|
||||
FileName string `json:"file_name"`
|
||||
URL string `json:"url"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
ThumbW float64 `json:"thumb_w"`
|
||||
ThumbH float64 `json:"thumb_h"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
ThumbSize float64 `json:"thumb_size"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
@@ -346,11 +376,34 @@ type ShareImageRequest struct {
|
||||
Images []ShareImageMeta `json:"images"`
|
||||
}
|
||||
|
||||
type ShareInfoResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
CoverImage string `json:"cover_image"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
VisitLimit int64 `json:"visit_limit"`
|
||||
ExpireTime string `json:"expire_time"`
|
||||
ImageCount int64 `json:"image_count"`
|
||||
VisitCount int64 `json:"visit_count"`
|
||||
ViewerCount int64 `json:"viewer_count"`
|
||||
SharerAvatar string `json:"sharer_avatar"`
|
||||
SharerName string `json:"sharer_name"`
|
||||
AlbumName string `json:"album_name"`
|
||||
}
|
||||
|
||||
type ShareOverviewResponse struct {
|
||||
VisitCount int64 `json:"visit_count"`
|
||||
VisitCountToday int64 `json:"visit_count_today"`
|
||||
ViewerCount int64 `json:"viewer_count"`
|
||||
ViewerCountToday int64 `json:"viewer_count_today"`
|
||||
PublishCount int64 `json:"publish_count"`
|
||||
PublishCountToday int64 `json:"publish_count_today"`
|
||||
}
|
||||
|
||||
type ShareRecord struct {
|
||||
ID int64 `json:"id"`
|
||||
CoverImage string `json:"cover_image"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ShareCode string `json:"share_code"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
VisitLimit int64 `json:"visit_limit"`
|
||||
AccessPassword string `json:"access_password"`
|
||||
ValidityPeriod int64 `json:"validity_period"`
|
||||
|
Reference in New Issue
Block a user