🚧 developing...
This commit is contained in:
@@ -659,6 +659,9 @@ type (
|
|||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
}
|
}
|
||||||
|
DownloadAlbumResponse {
|
||||||
|
Records []string `json:"records"`
|
||||||
|
}
|
||||||
// 搜索图片请求参数
|
// 搜索图片请求参数
|
||||||
SearchImageRequest {
|
SearchImageRequest {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -671,6 +674,21 @@ type (
|
|||||||
SearchImageResponse {
|
SearchImageResponse {
|
||||||
Records []AllImageDetail `json:"records"`
|
Records []AllImageDetail `json:"records"`
|
||||||
}
|
}
|
||||||
|
// 搜索相册请求参数
|
||||||
|
SearchAlbumRequest {
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
}
|
||||||
|
// 搜索相册相应参数
|
||||||
|
SearchAlbumResponse {
|
||||||
|
Albums []Album `json:"albums"`
|
||||||
|
}
|
||||||
|
// 图片添加到相册请求参数
|
||||||
|
AddImageToAlbumRequest {
|
||||||
|
IDS []int64 `json:"ids"`
|
||||||
|
AlbumID int64 `json:"album_id"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 文件上传
|
// 文件上传
|
||||||
@@ -780,11 +798,19 @@ service auth {
|
|||||||
|
|
||||||
// 下载相册
|
// 下载相册
|
||||||
@handler downloadAlbum
|
@handler downloadAlbum
|
||||||
post /album/download (DownloadAlbumRequest) returns (string)
|
post /album/download (DownloadAlbumRequest) returns (DownloadAlbumResponse)
|
||||||
|
|
||||||
// 图片搜索
|
// 图片搜索
|
||||||
@handler searchImage
|
@handler searchImage
|
||||||
post /image/search (SearchImageRequest) returns (SearchImageResponse)
|
post /image/search (SearchImageRequest) returns (SearchImageResponse)
|
||||||
|
|
||||||
|
// 搜索相册
|
||||||
|
@handler searchAlbum
|
||||||
|
post /album/search (SearchAlbumRequest) returns (SearchAlbumResponse)
|
||||||
|
|
||||||
|
// 添加图片到相册
|
||||||
|
@handler addImageToAlbum
|
||||||
|
post /album/add/image (AddImageToAlbumRequest) returns (string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@@ -257,6 +257,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
rest.WithMiddlewares(
|
rest.WithMiddlewares(
|
||||||
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.CasbinVerifyMiddleware, serverCtx.NonceMiddleware},
|
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.CasbinVerifyMiddleware, serverCtx.NonceMiddleware},
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/album/add/image",
|
||||||
|
Handler: storage.AddImageToAlbumHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Path: "/album/create",
|
Path: "/album/create",
|
||||||
@@ -287,6 +292,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||||||
Path: "/album/rename",
|
Path: "/album/rename",
|
||||||
Handler: storage.RenameAlbumHandler(serverCtx),
|
Handler: storage.RenameAlbumHandler(serverCtx),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/album/search",
|
||||||
|
Handler: storage.SearchAlbumHandler(serverCtx),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Path: "/album/share",
|
Path: "/album/share",
|
||||||
|
@@ -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 AddImageToAlbumHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.AddImageToAlbumRequest
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := storage.NewAddImageToAlbumLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.AddImageToAlbum(&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 SearchAlbumHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.SearchAlbumRequest
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := storage.NewSearchAlbumLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.SearchAlbum(&req)
|
||||||
|
if err != nil {
|
||||||
|
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ package share
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"schisandra-album-cloud-microservices/common/constant"
|
||||||
|
|
||||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||||
@@ -65,6 +66,19 @@ func (l *DeleteShareRecordLogic) DeleteShareRecord(req *types.DeleteShareRecordR
|
|||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return "", errors.New("delete storage info record failed")
|
return "", errors.New("delete storage info record failed")
|
||||||
}
|
}
|
||||||
|
// delete redis cache
|
||||||
|
cacheKey := constant.ImageSharePrefix + req.InviteCode
|
||||||
|
err = l.svcCtx.RedisClient.Del(l.ctx, cacheKey).Err()
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return "", errors.New("delete cache failed")
|
||||||
|
}
|
||||||
|
cacheVisitKey := constant.ImageShareVisitPrefix + req.InviteCode
|
||||||
|
err = l.svcCtx.RedisClient.Del(l.ctx, cacheVisitKey).Err()
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return "", errors.New("delete cache visit failed")
|
||||||
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
@@ -0,0 +1,44 @@
|
|||||||
|
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 AddImageToAlbumLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddImageToAlbumLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AddImageToAlbumLogic {
|
||||||
|
return &AddImageToAlbumLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *AddImageToAlbumLogic) AddImageToAlbum(req *types.AddImageToAlbumRequest) (resp string, err error) {
|
||||||
|
uid, ok := l.ctx.Value("user_id").(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("user_id not found")
|
||||||
|
}
|
||||||
|
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||||
|
update, err := storageInfo.Where(storageInfo.UserID.Eq(uid),
|
||||||
|
storageInfo.ID.In(req.IDS...),
|
||||||
|
storageInfo.Provider.Eq(req.Provider),
|
||||||
|
storageInfo.Bucket.Eq(req.Bucket)).Update(storageInfo.AlbumID, req.AlbumID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if update.RowsAffected == 0 {
|
||||||
|
return "", errors.New("no image found")
|
||||||
|
}
|
||||||
|
return "success", nil
|
||||||
|
}
|
@@ -2,6 +2,16 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"schisandra-album-cloud-microservices/app/auth/model/mysql/model"
|
||||||
|
"schisandra-album-cloud-microservices/common/constant"
|
||||||
|
"schisandra-album-cloud-microservices/common/encrypt"
|
||||||
|
storageConfig "schisandra-album-cloud-microservices/common/storage/config"
|
||||||
|
"time"
|
||||||
|
|
||||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||||
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||||
@@ -23,8 +33,115 @@ func NewDownloadAlbumLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Dow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *DownloadAlbumLogic) DownloadAlbum(req *types.DownloadAlbumRequest) (resp string, err error) {
|
func (l *DownloadAlbumLogic) DownloadAlbum(req *types.DownloadAlbumRequest) (resp *types.DownloadAlbumResponse, err error) {
|
||||||
// todo: download album logic
|
uid, ok := l.ctx.Value("user_id").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("user_id not found")
|
||||||
|
}
|
||||||
|
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||||
|
storageInfos, err := storageInfo.Where(storageInfo.UserID.Eq(uid), storageInfo.AlbumID.Eq(req.ID),
|
||||||
|
storageInfo.Provider.Eq(req.Provider), storageInfo.Bucket.Eq(req.Bucket)).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return
|
// 加载用户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")
|
||||||
|
}
|
||||||
|
// 并发生成预签名URL
|
||||||
|
urls := make([]string, len(storageInfos))
|
||||||
|
g, ctx := errgroup.WithContext(l.ctx)
|
||||||
|
for i := range storageInfos {
|
||||||
|
i := i
|
||||||
|
file := storageInfos[i]
|
||||||
|
g.Go(func() error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
url, err := service.PresignedURL(ctx, ossConfig.BucketName, file.Path, 5*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate URL for %s: %w", file.Path, err)
|
||||||
|
}
|
||||||
|
urls[i] = url
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = g.Wait(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate URLs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.DownloadAlbumResponse{
|
||||||
|
Records: urls,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取解密操作为函数
|
||||||
|
func (l *DownloadAlbumLogic) 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 *DownloadAlbumLogic) 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
|
||||||
}
|
}
|
||||||
|
51
app/auth/api/internal/logic/storage/search_album_logic.go
Normal file
51
app/auth/api/internal/logic/storage/search_album_logic.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||||
|
"schisandra-album-cloud-microservices/app/auth/api/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchAlbumLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchAlbumLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SearchAlbumLogic {
|
||||||
|
return &SearchAlbumLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SearchAlbumLogic) SearchAlbum(req *types.SearchAlbumRequest) (resp *types.SearchAlbumResponse, err error) {
|
||||||
|
uid, ok := l.ctx.Value("user_id").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("user_id not found")
|
||||||
|
}
|
||||||
|
storageAlbum := l.svcCtx.DB.ScaStorageAlbum
|
||||||
|
storageAlbums, err := storageAlbum.Where(storageAlbum.UserID.Eq(uid), storageAlbum.AlbumName.Like("%"+req.Keyword+"%")).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(storageAlbums) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var albums []types.Album
|
||||||
|
for _, album := range storageAlbums {
|
||||||
|
albums = append(albums, types.Album{
|
||||||
|
ID: album.ID,
|
||||||
|
Name: album.AlbumName,
|
||||||
|
Type: album.AlbumType,
|
||||||
|
CoverImage: album.CoverImage,
|
||||||
|
CreatedAt: album.CreatedAt.Format("2006-01-02"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &types.SearchAlbumResponse{
|
||||||
|
Albums: albums,
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -11,6 +11,13 @@ type AccountLoginRequest struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddImageToAlbumRequest struct {
|
||||||
|
IDS []int64 `json:"ids"`
|
||||||
|
AlbumID int64 `json:"album_id"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
}
|
||||||
|
|
||||||
type Album struct {
|
type Album struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -181,6 +188,10 @@ type DownloadAlbumRequest struct {
|
|||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DownloadAlbumResponse struct {
|
||||||
|
Records []string `json:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
type FaceDetailListRequest struct {
|
type FaceDetailListRequest struct {
|
||||||
FaceID int64 `json:"face_id"`
|
FaceID int64 `json:"face_id"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
@@ -364,6 +375,14 @@ type RotateCaptchaResponse struct {
|
|||||||
Thumb string `json:"thumb"`
|
Thumb string `json:"thumb"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchAlbumRequest struct {
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchAlbumResponse struct {
|
||||||
|
Albums []Album `json:"albums"`
|
||||||
|
}
|
||||||
|
|
||||||
type SearchImageRequest struct {
|
type SearchImageRequest struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Keyword string `json:"keyword"`
|
Keyword string `json:"keyword"`
|
||||||
|
Reference in New Issue
Block a user