✨ develop basic APIs / override reverse geolocation
This commit is contained in:
@@ -243,6 +243,31 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/image/all/list",
|
||||
Handler: storage.QueryAllImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/location/detail/list",
|
||||
Handler: storage.QueryLocationDetailListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/location/list",
|
||||
Handler: storage.QueryLocationImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/recent/list",
|
||||
Handler: storage.QueryRecentImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/thing/detail/list",
|
||||
Handler: storage.QueryThingDetailListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/thing/list",
|
||||
Handler: storage.QueryThingImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/uploads",
|
||||
|
@@ -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 QueryLocationDetailListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.LocationDetailListRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewQueryLocationDetailListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryLocationDetailList(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func QueryLocationImageListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := storage.NewQueryLocationImageListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryLocationImageList()
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func QueryRecentImageListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := storage.NewQueryRecentImageListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryRecentImageList()
|
||||
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 QueryThingDetailListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.ThingDetailListRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewQueryThingDetailListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryThingDetailList(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func QueryThingImageListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := storage.NewQueryThingImageListLogic(r.Context(), svcCtx)
|
||||
resp, err := l.QueryThingImageList()
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,8 +16,6 @@ type DeleteAlbumLogic struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
//43454A
|
||||
|
||||
func NewDeleteAlbumLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteAlbumLogic {
|
||||
return &DeleteAlbumLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
@@ -39,7 +37,7 @@ func (l *DeleteAlbumLogic) DeleteAlbum(req *types.AlbumDeleteRequest) (resp stri
|
||||
return "", errors.New("album not found")
|
||||
}
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
_, err = storageInfo.Where(storageInfo.AlbumID.Eq(req.ID), storageInfo.UserID.Eq(uid)).Update(storageInfo.AlbumID, nil)
|
||||
_, err = storageInfo.Where(storageInfo.AlbumID.Eq(req.ID), storageInfo.UserID.Eq(uid)).Update(storageInfo.AlbumID, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ func (l *ModifyFaceLibraryTypeLogic) ModifyFaceLibraryType(req *types.ModifyFace
|
||||
return nil, err
|
||||
}
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
resultInfo, err := storageInfo.Where(storageInfo.FaceID.In(req.IDs...)).Update(storageInfo.Show, req.FaceType)
|
||||
resultInfo, err := storageInfo.Where(storageInfo.FaceID.In(req.IDs...)).Update(storageInfo.ImgShow, req.FaceType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -25,6 +25,16 @@ type QueryAllImageListLogic struct {
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
var WeekdayMap = map[time.Weekday]string{
|
||||
time.Sunday: "日",
|
||||
time.Monday: "一",
|
||||
time.Tuesday: "二",
|
||||
time.Wednesday: "三",
|
||||
time.Thursday: "四",
|
||||
time.Friday: "五",
|
||||
time.Saturday: "六",
|
||||
}
|
||||
|
||||
func NewQueryAllImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryAllImageListLogic {
|
||||
return &QueryAllImageListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
@@ -102,7 +112,8 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
|
||||
wg.Add(1)
|
||||
go func(dbFileInfo *model.ScaStorageInfo) {
|
||||
defer wg.Done()
|
||||
date := dbFileInfo.CreatedAt.Format("2006-01-02")
|
||||
weekday := WeekdayMap[dbFileInfo.CreatedAt.Weekday()]
|
||||
date := dbFileInfo.CreatedAt.Format("2006年1月2日 星期" + weekday)
|
||||
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, dbFileInfo.Path, time.Hour*24*7)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
@@ -115,12 +126,10 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
|
||||
images = append(images, types.ImageMeta{
|
||||
ID: dbFileInfo.ID,
|
||||
FileName: dbFileInfo.FileName,
|
||||
FilePath: dbFileInfo.Path,
|
||||
URL: url,
|
||||
FileSize: dbFileInfo.FileSize,
|
||||
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
Width: dbFileInfo.Width,
|
||||
Height: dbFileInfo.Height,
|
||||
CreatedAt: dbFileInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
|
||||
// 重新存储更新后的图像列表
|
||||
|
@@ -0,0 +1,30 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 QueryLocationDetailListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryLocationDetailListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryLocationDetailListLogic {
|
||||
return &QueryLocationDetailListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.LocationDetailListRequest) (resp *types.LocationDetailListResponse, err error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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 QueryLocationImageListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryLocationImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryLocationImageListLogic {
|
||||
return &QueryLocationImageListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryLocationImageListLogic) QueryLocationImageList() (resp *types.LocationListResponse, err error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
storageLocation := l.svcCtx.DB.ScaStorageLocation
|
||||
|
||||
locations, err := storageLocation.Select(
|
||||
storageLocation.ID,
|
||||
storageLocation.Country,
|
||||
storageLocation.City,
|
||||
storageLocation.Province,
|
||||
storageLocation.Total).Where(storageLocation.UserID.Eq(uid)).
|
||||
Order(storageLocation.CreatedAt.Desc()).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locationMap := make(map[string][]types.LocationMeta)
|
||||
|
||||
for _, loc := range locations {
|
||||
var locationKey string
|
||||
if loc.Province == "" {
|
||||
locationKey = loc.Country
|
||||
} else {
|
||||
locationKey = fmt.Sprintf("%s %s", loc.Country, loc.Province)
|
||||
}
|
||||
|
||||
city := loc.City
|
||||
if city == "" {
|
||||
city = loc.Country
|
||||
}
|
||||
locationMeta := types.LocationMeta{
|
||||
ID: loc.ID,
|
||||
City: city,
|
||||
Total: loc.Total,
|
||||
}
|
||||
locationMap[locationKey] = append(locationMap[locationKey], locationMeta)
|
||||
}
|
||||
|
||||
var locationListData []types.LocationListData
|
||||
|
||||
for location, list := range locationMap {
|
||||
locationListData = append(locationListData, types.LocationListData{
|
||||
Location: location,
|
||||
List: list,
|
||||
})
|
||||
}
|
||||
|
||||
return &types.LocationListResponse{Records: locationListData}, nil
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"schisandra-album-cloud-microservices/common/constant"
|
||||
"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 QueryRecentImageListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryRecentImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryRecentImageListLogic {
|
||||
return &QueryRecentImageListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryRecentImageListLogic) QueryRecentImageList() (resp *types.RecentListResponse, err error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
|
||||
redisKeyPattern := constant.ImageRecentPrefix + uid + ":*"
|
||||
iter := l.svcCtx.RedisClient.Scan(l.ctx, 0, redisKeyPattern, 0).Iterator()
|
||||
var keys []string
|
||||
for iter.Next(l.ctx) {
|
||||
keys = append(keys, iter.Val())
|
||||
}
|
||||
if err := iter.Err(); err != nil {
|
||||
logx.Error(err)
|
||||
return nil, errors.New("scan recent file list failed")
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return &types.RecentListResponse{Records: []types.AllImageDetail{}}, nil
|
||||
}
|
||||
|
||||
cmds, err := l.svcCtx.RedisClient.MGet(l.ctx, keys...).Result()
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return nil, errors.New("get recent file list failed")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
groupedImages := sync.Map{}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
if cmd == nil {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(cmd interface{}) {
|
||||
defer wg.Done()
|
||||
val, ok := cmd.(string)
|
||||
if !ok {
|
||||
logx.Error("invalid value type")
|
||||
return
|
||||
}
|
||||
var imageMeta types.ImageMeta
|
||||
err = json.Unmarshal([]byte(val), &imageMeta)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
parse, err := time.Parse("2006-01-02 15:04:05", imageMeta.CreatedAt)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
}
|
||||
date := parse.Format("2006年1月2日 星期" + WeekdayMap[parse.Weekday()])
|
||||
groupedImages.Range(func(key, value interface{}) bool {
|
||||
if key == date {
|
||||
images := value.([]types.ImageMeta)
|
||||
images = append(images, imageMeta)
|
||||
groupedImages.Store(date, images)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
groupedImages.Store(date, []types.ImageMeta{imageMeta})
|
||||
}(cmd)
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
return &types.RecentListResponse{
|
||||
Records: imageList,
|
||||
}, nil
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 QueryThingDetailListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryThingDetailListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryThingDetailListLogic {
|
||||
return &QueryThingDetailListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailListRequest) (resp *types.ThingDetailListResponse, err error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"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 QueryThingImageListLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewQueryThingImageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryThingImageListLogic {
|
||||
return &QueryThingImageListLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryThingImageListLogic) QueryThingImageList() (resp *types.ThingListResponse, err error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
storageInfos, err := storageInfo.Select(
|
||||
storageInfo.ID,
|
||||
storageInfo.Category,
|
||||
storageInfo.Tags,
|
||||
storageInfo.CreatedAt).
|
||||
Where(storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Category.IsNotNull(),
|
||||
storageInfo.Tags.IsNotNull()).
|
||||
Order(storageInfo.CreatedAt.Desc()).
|
||||
Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
categoryMap := sync.Map{}
|
||||
tagCountMap := sync.Map{}
|
||||
|
||||
for _, info := range storageInfos {
|
||||
tagKey := info.Category + "::" + info.Tags
|
||||
if _, exists := tagCountMap.Load(tagKey); !exists {
|
||||
tagCountMap.Store(tagKey, int64(0))
|
||||
categoryEntry, _ := categoryMap.LoadOrStore(info.Category, &sync.Map{})
|
||||
tagMap := categoryEntry.(*sync.Map)
|
||||
tagMap.Store(info.Tags, types.ThingMeta{
|
||||
TagName: info.Tags,
|
||||
CreatedAt: info.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
tagCount, _ := tagCountMap.Load(tagKey)
|
||||
tagCountMap.Store(tagKey, tagCount.(int64)+1)
|
||||
}
|
||||
|
||||
var thingListData []types.ThingListData
|
||||
categoryMap.Range(func(category, tagData interface{}) bool {
|
||||
var metas []types.ThingMeta
|
||||
tagData.(*sync.Map).Range(func(tag, item interface{}) bool {
|
||||
tagKey := category.(string) + "::" + tag.(string)
|
||||
tagCount, _ := tagCountMap.Load(tagKey)
|
||||
meta := item.(types.ThingMeta)
|
||||
meta.TagCount = tagCount.(int64)
|
||||
metas = append(metas, meta)
|
||||
return true
|
||||
})
|
||||
thingListData = append(thingListData, types.ThingListData{
|
||||
Category: category.(string),
|
||||
List: metas,
|
||||
})
|
||||
return true
|
||||
})
|
||||
|
||||
return &types.ThingListResponse{Records: thingListData}, nil
|
||||
}
|
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/ccpwcn/kgo"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"gorm.io/gorm"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
@@ -19,10 +20,11 @@ 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/gao_map"
|
||||
"schisandra-album-cloud-microservices/common/geo_json"
|
||||
"schisandra-album-cloud-microservices/common/storage/config"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -30,6 +32,7 @@ type UploadFileLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewUploadFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadFileLogic {
|
||||
@@ -37,6 +40,7 @@ func NewUploadFileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Upload
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
wg: sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +71,7 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 人脸识别
|
||||
if result.FileType == "image/png" || result.FileType == "image/jpeg" {
|
||||
face, err := l.svcCtx.AiSvcRpc.FaceRecognition(l.ctx, &pb.FaceRecognitionRequest{Face: bytes, UserId: uid})
|
||||
@@ -76,13 +81,6 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
if face != nil {
|
||||
faceId = face.GetFaceId()
|
||||
}
|
||||
|
||||
//// 图像分类
|
||||
//classification, err := l.svcCtx.AiSvcRpc.TfClassification(l.ctx, &pb.TfClassificationRequest{Image: bytes})
|
||||
//if err != nil {
|
||||
// return "", err
|
||||
//}
|
||||
//className = classification.GetClassName()
|
||||
}
|
||||
|
||||
// 解析 EXIF 信息
|
||||
@@ -101,7 +99,13 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
latitude, longitude := l.extractGPSCoordinates(exif)
|
||||
|
||||
// 根据 GPS 信息获取地理位置信息
|
||||
locationString, gpsString, err := l.getGeoLocation(latitude, longitude)
|
||||
country, province, city, err := l.getGeoLocation(latitude, longitude)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 将地址信息保存到数据库
|
||||
locationId, err := l.saveFileLocationInfoToDB(uid, latitude, longitude, country, province, city)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -111,19 +115,25 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
if _, err = file.Seek(0, 0); err != nil {
|
||||
return "", err
|
||||
}
|
||||
bucket, provider, filePath, err := l.uploadFileToOSS(uid, header, file, result)
|
||||
bucket, provider, filePath, url, err := l.uploadFileToOSS(uid, header, file, result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 将 EXIF 和文件信息存入数据库
|
||||
if err = l.saveFileInfoToDB(uid, bucket, provider, header, result, originalDateTime, gpsString, locationString, exif, faceId, filePath); err != nil {
|
||||
id, err := l.saveFileInfoToDB(uid, bucket, provider, header, result, originalDateTime, locationId, exif, 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
|
||||
}
|
||||
|
||||
@@ -138,16 +148,10 @@ func (l *UploadFileLogic) getUserID() (string, error) {
|
||||
|
||||
// 在UploadImageLogic或其他需要使缓存失效的逻辑中添加:
|
||||
func (l *UploadFileLogic) afterImageUpload(uid, provider, bucket string) {
|
||||
// 构造所有可能的缓存键组合(sort为true/false)
|
||||
keysToDelete := []string{
|
||||
fmt.Sprintf("%s%s:%s:%s:true", constant.ImageListPrefix, uid, provider, bucket),
|
||||
fmt.Sprintf("%s%s:%s:%s:false", constant.ImageListPrefix, uid, provider, bucket),
|
||||
}
|
||||
|
||||
// 批量删除缓存
|
||||
for _, key := range keysToDelete {
|
||||
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("Failed to delete cache key %s: %v", key, err)
|
||||
logx.Errorf("删除缓存键 %s 失败: %v", key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,6 +177,9 @@ func (l *UploadFileLogic) parseAIRecognitionResult(r *http.Request) (types.File,
|
||||
|
||||
// 解析 EXIF 数据
|
||||
func (l *UploadFileLogic) parseExifData(exifData interface{}) (map[string]interface{}, error) {
|
||||
if exifData == "" {
|
||||
return nil, nil
|
||||
}
|
||||
marshaledExif, err := json.Marshal(exifData)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid exif")
|
||||
@@ -209,58 +216,53 @@ func (l *UploadFileLogic) extractGPSCoordinates(exif map[string]interface{}) (fl
|
||||
}
|
||||
|
||||
// 根据 GPS 信息获取地理位置信息
|
||||
func (l *UploadFileLogic) getGeoLocation(latitude, longitude float64) (string, string, error) {
|
||||
if latitude == 0 || longitude == 0 {
|
||||
return "", "", nil
|
||||
func (l *UploadFileLogic) getGeoLocation(latitude, longitude float64) (string, string, string, error) {
|
||||
if latitude == 0.000000 || longitude == 0.000000 {
|
||||
return "", "", "", nil
|
||||
}
|
||||
|
||||
gpsString := fmt.Sprintf("[%f,%f]", latitude, longitude)
|
||||
request := gao_map.ReGeoRequest{Location: fmt.Sprintf("%f,%f", latitude, longitude)}
|
||||
//request := gao_map.ReGeoRequest{Location: fmt.Sprintf("%f,%f", latitude, longitude)}
|
||||
//
|
||||
//location, err := l.svcCtx.GaoMap.Location.ReGeo(&request)
|
||||
//if err != nil {
|
||||
// return nil, errors.New("regeo failed")
|
||||
//}
|
||||
//
|
||||
//addressInfo := map[string]string{}
|
||||
//if location.ReGeoCode.AddressComponent.Country != "" {
|
||||
// addressInfo["country"] = location.ReGeoCode.AddressComponent.Country
|
||||
//}
|
||||
//if location.ReGeoCode.AddressComponent.Province != "" {
|
||||
// addressInfo["province"] = location.ReGeoCode.AddressComponent.Province
|
||||
//}
|
||||
//if location.ReGeoCode.AddressComponent.City != "" {
|
||||
// addressInfo["city"] = location.ReGeoCode.AddressComponent.City.(string)
|
||||
//}
|
||||
//if location.ReGeoCode.AddressComponent.District != "" {
|
||||
// addressInfo["district"] = location.ReGeoCode.AddressComponent.District.(string)
|
||||
//}
|
||||
//if location.ReGeoCode.AddressComponent.Township != "" {
|
||||
// addressInfo["township"] = location.ReGeoCode.AddressComponent.Township
|
||||
//}
|
||||
|
||||
location, err := l.svcCtx.GaoMap.Location.ReGeo(&request)
|
||||
country, province, city, err := geo_json.GetAddress(latitude, longitude, l.svcCtx.GeoRegionData)
|
||||
if err != nil {
|
||||
return "", "", errors.New("regeo failed")
|
||||
return "", "", "", errors.New("get geo location failed")
|
||||
}
|
||||
|
||||
addressInfo := map[string]string{}
|
||||
if location.ReGeoCode.AddressComponent.Country != "" {
|
||||
addressInfo["county"] = location.ReGeoCode.AddressComponent.Country
|
||||
}
|
||||
if location.ReGeoCode.AddressComponent.Province != "" {
|
||||
addressInfo["province"] = location.ReGeoCode.AddressComponent.Province
|
||||
}
|
||||
if location.ReGeoCode.AddressComponent.City != "" {
|
||||
addressInfo["city"] = location.ReGeoCode.AddressComponent.City.(string)
|
||||
}
|
||||
if location.ReGeoCode.AddressComponent.District != "" {
|
||||
addressInfo["district"] = location.ReGeoCode.AddressComponent.District.(string)
|
||||
}
|
||||
if location.ReGeoCode.AddressComponent.Township != "" {
|
||||
addressInfo["township"] = location.ReGeoCode.AddressComponent.Township
|
||||
}
|
||||
|
||||
locationString := ""
|
||||
if len(addressInfo) > 0 {
|
||||
addressJSON, err := json.Marshal(addressInfo)
|
||||
if err != nil {
|
||||
return "", "", errors.New("marshal address info failed")
|
||||
}
|
||||
locationString = string(addressJSON)
|
||||
}
|
||||
|
||||
return locationString, gpsString, nil
|
||||
return country, province, city, nil
|
||||
}
|
||||
|
||||
// 上传文件到 OSS
|
||||
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, string, string, error) {
|
||||
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, string, string, string, error) {
|
||||
cacheKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
|
||||
ossConfig, err := l.getOssConfigFromCacheOrDb(cacheKey, uid, result.Provider)
|
||||
if err != nil {
|
||||
return "", "", "", errors.New("get oss config failed")
|
||||
return "", "", "", "", errors.New("get oss config failed")
|
||||
}
|
||||
service, err := l.svcCtx.StorageManager.GetStorage(uid, ossConfig)
|
||||
if err != nil {
|
||||
return "", "", "", errors.New("get storage failed")
|
||||
return "", "", "", "", errors.New("get storage failed")
|
||||
}
|
||||
|
||||
objectKey := path.Join(
|
||||
@@ -273,16 +275,56 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
|
||||
"Content-Type": header.Header.Get("Content-Type"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", "", errors.New("upload file failed")
|
||||
return "", "", "", "", errors.New("upload file failed")
|
||||
}
|
||||
url, err := service.PresignedURL(l.ctx, ossConfig.BucketName, objectKey, time.Hour*24*7)
|
||||
if err != nil {
|
||||
return "", "", "", "", errors.New("presigned url failed")
|
||||
}
|
||||
return ossConfig.BucketName, ossConfig.Provider, objectKey, url, nil
|
||||
}
|
||||
|
||||
func (l *UploadFileLogic) saveFileLocationInfoToDB(uid string, latitude float64, longitude float64, country string, province string, city 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{
|
||||
UserID: uid,
|
||||
Country: country,
|
||||
City: city,
|
||||
Province: province,
|
||||
Latitude: fmt.Sprintf("%f", latitude),
|
||||
Longitude: fmt.Sprintf("%f", longitude),
|
||||
Total: 1,
|
||||
}
|
||||
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)).Update(locationDB.Total, locationDB.Total.Add(1))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
return 0, errors.New("update location failed")
|
||||
}
|
||||
return storageLocations.ID, nil
|
||||
}
|
||||
return ossConfig.BucketName, ossConfig.Provider, objectKey, nil
|
||||
}
|
||||
|
||||
// 将 EXIF 和文件信息存入数据库
|
||||
func (l *UploadFileLogic) saveFileInfoToDB(uid, bucket, provider string, header *multipart.FileHeader, result types.File, originalDateTime, gpsString, locationString string, exif map[string]interface{}, faceId int64, filePath string) error {
|
||||
func (l *UploadFileLogic) saveFileInfoToDB(uid, bucket, provider string, header *multipart.FileHeader, result types.File, originalDateTime string, locationId int64, exif map[string]interface{}, faceId int64, filePath string) (int64, error) {
|
||||
exifJSON, err := json.Marshal(exif)
|
||||
if err != nil {
|
||||
return errors.New("marshal exif failed")
|
||||
return 0, errors.New("marshal exif failed")
|
||||
}
|
||||
typeName := l.classifyFile(result.FileType, result.IsScreenshot)
|
||||
scaStorageInfo := &model.ScaStorageInfo{
|
||||
@@ -294,13 +336,12 @@ func (l *UploadFileLogic) saveFileInfoToDB(uid, bucket, provider string, header
|
||||
FileType: result.FileType,
|
||||
Path: filePath,
|
||||
Landscape: result.Landscape,
|
||||
Tags: strings.Join(result.ObjectArray, ","),
|
||||
Tags: result.TagName,
|
||||
Anime: strconv.FormatBool(result.IsAnime),
|
||||
Category: result.TopCategory,
|
||||
Screenshot: strconv.FormatBool(result.IsScreenshot),
|
||||
OriginalTime: originalDateTime,
|
||||
Gps: gpsString,
|
||||
Location: locationString,
|
||||
LocationID: locationId,
|
||||
Exif: string(exifJSON),
|
||||
FaceID: faceId,
|
||||
Type: typeName,
|
||||
@@ -310,9 +351,9 @@ func (l *UploadFileLogic) saveFileInfoToDB(uid, bucket, provider string, header
|
||||
|
||||
err = l.svcCtx.DB.ScaStorageInfo.Create(scaStorageInfo)
|
||||
if err != nil {
|
||||
return errors.New("create storage info failed")
|
||||
return 0, errors.New("create storage info failed")
|
||||
}
|
||||
return nil
|
||||
return scaStorageInfo.ID, nil
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
@@ -404,3 +445,28 @@ 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
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/app/auth/model/mysql/query"
|
||||
"schisandra-album-cloud-microservices/common/captcha/initialize"
|
||||
"schisandra-album-cloud-microservices/common/casbinx"
|
||||
"schisandra-album-cloud-microservices/common/gao_map"
|
||||
"schisandra-album-cloud-microservices/common/geo_json"
|
||||
"schisandra-album-cloud-microservices/common/ip2region"
|
||||
"schisandra-album-cloud-microservices/common/miniox"
|
||||
"schisandra-album-cloud-microservices/common/redisx"
|
||||
@@ -43,8 +43,8 @@ type ServiceContext struct {
|
||||
SlideCaptcha slide.Captcha
|
||||
Sensitive *sensitive.Manager
|
||||
StorageManager *manager.Manager
|
||||
GaoMap *gao_map.AmapClient
|
||||
MinioClient *minio.Client
|
||||
GeoRegionData *geo_json.RegionData
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
@@ -65,8 +65,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
SlideCaptcha: initialize.NewSlideCaptcha(),
|
||||
Sensitive: sensitivex.NewSensitive(),
|
||||
StorageManager: storage.InitStorageManager(),
|
||||
GaoMap: gao_map.NewAmapClient(c.Map.Key, ""),
|
||||
AiSvcRpc: aiservice.NewAiService(zrpc.MustNewClient(c.AiSvcRpc)),
|
||||
MinioClient: miniox.NewMinio(c.Minio.Endpoint, c.Minio.AccessKeyID, c.Minio.SecretAccessKey, c.Minio.UseSSL),
|
||||
GeoRegionData: geo_json.NewGeoJSON(),
|
||||
}
|
||||
}
|
||||
|
@@ -2,15 +2,15 @@ package types
|
||||
|
||||
// File represents a file uploaded by the user.
|
||||
type File struct {
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
FileType string `json:"fileType"`
|
||||
IsAnime bool `json:"isAnime"`
|
||||
ObjectArray []string `json:"objectArray"`
|
||||
Landscape string `json:"landscape"`
|
||||
TopCategory string `json:"topCategory"`
|
||||
IsScreenshot bool `json:"isScreenshot"`
|
||||
Exif any `json:"exif"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
FileType string `json:"fileType"`
|
||||
IsAnime bool `json:"isAnime"`
|
||||
TagName string `json:"tagName"`
|
||||
Landscape string `json:"landscape"`
|
||||
TopCategory string `json:"topCategory"`
|
||||
IsScreenshot bool `json:"isScreenshot"`
|
||||
Exif any `json:"exif"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
}
|
||||
|
@@ -162,12 +162,33 @@ type FaceSampleLibraryListResponse struct {
|
||||
type ImageMeta struct {
|
||||
ID int64 `json:"id"`
|
||||
FileName string `json:"file_name"`
|
||||
FilePath string `json:"file_path"`
|
||||
URL string `json:"url"`
|
||||
FileSize string `json:"file_size"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type LocationDetailListRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type LocationDetailListResponse struct {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
|
||||
type LocationListData struct {
|
||||
Location string `json:"location"` // 中国 新疆维吾尔自治区
|
||||
List []LocationMeta `json:"list"` // 图片列表
|
||||
}
|
||||
|
||||
type LocationListResponse struct {
|
||||
Records []LocationListData `json:"records"`
|
||||
}
|
||||
|
||||
type LocationMeta struct {
|
||||
ID int64 `json:"id"`
|
||||
City string `json:"city"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
@@ -212,6 +233,10 @@ type PhoneLoginRequest struct {
|
||||
AutoLogin bool `json:"auto_login"`
|
||||
}
|
||||
|
||||
type RecentListResponse struct {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
|
||||
type RefreshTokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpireAt int64 `json:"expire_at"`
|
||||
@@ -285,6 +310,29 @@ type StorageConfigRequest struct {
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
type ThingDetailListRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
type ThingDetailListResponse struct {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
|
||||
type ThingListData struct {
|
||||
Category string `json:"category"` // 分类
|
||||
List []ThingMeta `json:"list"` // 图片列表
|
||||
}
|
||||
|
||||
type ThingListResponse struct {
|
||||
Records []ThingListData `json:"records"`
|
||||
}
|
||||
|
||||
type ThingMeta struct {
|
||||
TagName string `json:"tag_name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
TagCount int64 `json:"tag_count"`
|
||||
}
|
||||
|
||||
type UploadRequest struct {
|
||||
Image string `json:"image"`
|
||||
AccessToken string `json:"access_token"`
|
||||
|
Reference in New Issue
Block a user