✨ add search function
This commit is contained in:
@@ -363,7 +363,7 @@ type (
|
||||
group: comment // 微服务分组
|
||||
prefix: /api/auth/comment // 微服务前缀
|
||||
timeout: 10s // 超时时间
|
||||
maxBytes: 1048576 // 最大请求大小
|
||||
maxBytes: 10485760 // 最大请求大小
|
||||
signature: false // 是否开启签名验证
|
||||
middleware: SecurityHeadersMiddleware,CasbinVerifyMiddleware,NonceMiddleware // 注册中间件
|
||||
MaxConns: true // 是否开启最大连接数限制
|
||||
@@ -426,6 +426,9 @@ service auth {
|
||||
|
||||
@handler sharePhoneUpload
|
||||
post /share/upload (SharePhoneUploadRequest)
|
||||
|
||||
@handler commonUpload
|
||||
post /common/upload
|
||||
}
|
||||
|
||||
// 文件上传配置请求参数
|
||||
@@ -656,6 +659,18 @@ type (
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
// 搜索图片请求参数
|
||||
SearchImageRequest {
|
||||
Type string `json:"type"`
|
||||
Keyword string `json:"keyword"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
InputImage string `json:"input_image,omitempty"`
|
||||
}
|
||||
// 搜索图片相应参数
|
||||
SearchImageResponse {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
)
|
||||
|
||||
// 文件上传
|
||||
@@ -766,6 +781,10 @@ service auth {
|
||||
// 下载相册
|
||||
@handler downloadAlbum
|
||||
post /album/download (DownloadAlbumRequest) returns (string)
|
||||
|
||||
// 图片搜索
|
||||
@handler searchImage
|
||||
post /image/search (SearchImageRequest) returns (SearchImageResponse)
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -810,7 +829,8 @@ type (
|
||||
records []ShareRecord `json:"records"`
|
||||
}
|
||||
QueryShareInfoRequest {
|
||||
InviteCode string `json:"invite_code"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
AccessPassword string `json:"access_password,omitempty"`
|
||||
}
|
||||
ShareInfoResponse {
|
||||
ID int64 `json:"id"`
|
||||
@@ -824,6 +844,8 @@ type (
|
||||
SharerAvatar string `json:"sharer_avatar"`
|
||||
SharerName string `json:"sharer_name"`
|
||||
AlbumName string `json:"album_name"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
SharerUID string `json:"sharer_uid"`
|
||||
}
|
||||
// 分享数据概览响应参数
|
||||
ShareOverviewResponse {
|
||||
|
@@ -175,3 +175,7 @@ NSQ:
|
||||
# NSQD地址
|
||||
NSQDHost: 1.95.0.111:4150
|
||||
LookUpdHost: 1.95.0.111:4161
|
||||
Zinc:
|
||||
URL: http://1.95.0.111:4080
|
||||
Username: landaiqing
|
||||
Password: LDQ20020618xxx
|
||||
|
@@ -77,4 +77,9 @@ type Config struct {
|
||||
NSQDHost string
|
||||
LookUpdHost string
|
||||
}
|
||||
Zinc struct {
|
||||
URL string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
}
|
||||
|
17
app/auth/api/internal/handler/phone/common_upload_handler.go
Normal file
17
app/auth/api/internal/handler/phone/common_upload_handler.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package phone
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/logic/phone"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
"schisandra-album-cloud-microservices/common/xhttp"
|
||||
)
|
||||
|
||||
func CommonUploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := phone.NewCommonUploadLogic(r.Context(), svcCtx)
|
||||
err := l.CommonUpload()
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
}
|
||||
}
|
@@ -105,7 +105,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
|
||||
rest.WithPrefix("/api/auth/comment"),
|
||||
rest.WithTimeout(10000*time.Millisecond),
|
||||
rest.WithMaxBytes(1048576),
|
||||
rest.WithMaxBytes(10485760),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
@@ -163,6 +163,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/common/upload",
|
||||
Handler: phone.CommonUploadHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/share/upload",
|
||||
@@ -347,6 +352,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
Path: "/image/recent/list",
|
||||
Handler: storage.QueryRecentImageListHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/search",
|
||||
Handler: storage.SearchImageHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/image/thing/detail/list",
|
||||
|
@@ -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 SearchImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.SearchImageRequest
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := storage.NewSearchImageLogic(r.Context(), svcCtx)
|
||||
resp, err := l.SearchImage(&req)
|
||||
if err != nil {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
|
||||
} else {
|
||||
xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
|
||||
}
|
||||
}
|
||||
}
|
28
app/auth/api/internal/logic/phone/common_upload_logic.go
Normal file
28
app/auth/api/internal/logic/phone/common_upload_logic.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package phone
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"schisandra-album-cloud-microservices/app/auth/api/internal/svc"
|
||||
)
|
||||
|
||||
type CommonUploadLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewCommonUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommonUploadLogic {
|
||||
return &CommonUploadLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CommonUploadLogic) CommonUpload() error {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return nil
|
||||
}
|
@@ -48,12 +48,14 @@ func (l *QueryShareInfoLogic) QueryShareInfo(req *types.QueryShareInfoRequest) (
|
||||
shareVisit.Views.As("visit_count"),
|
||||
shareVisit.UserID.Count().As("viewer_count"),
|
||||
authUser.Avatar.As("sharer_avatar"),
|
||||
authUser.Nickname.As("sharer_name")).
|
||||
authUser.Nickname.As("sharer_name"),
|
||||
authUser.UID.As("sharer_uid")).
|
||||
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),
|
||||
storageShare.AccessPassword.Eq(req.AccessPassword),
|
||||
shareVisit.UserID.Eq(uid)).
|
||||
Group(
|
||||
storageShare.ID,
|
||||
|
@@ -182,7 +182,7 @@ func (l *GetAlbumDetailLogic) GetAlbumDetail(req *types.AlbumDetailListRequest)
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -156,7 +156,7 @@ func (l *GetDeleteRecordLogic) GetDeleteRecord(req *types.QueryDeleteRecordReque
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -156,7 +156,7 @@ func (l *GetFaceDetailListLogic) GetFaceDetailList(req *types.FaceDetailListRequ
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -189,7 +189,7 @@ func (l *QueryAllImageListLogic) QueryAllImageList(req *types.AllImageListReques
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -160,7 +160,7 @@ func (l *QueryLocationDetailListLogic) QueryLocationDetailList(req *types.Locati
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*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,17 +40,18 @@ func (l *QueryLocationImageListLogic) QueryLocationImageList(req *types.Location
|
||||
storageInfo := l.svcCtx.DB.ScaStorageInfo
|
||||
|
||||
var locations []types.LocationInfo
|
||||
err = storageLocation.Select(
|
||||
err = storageInfo.Select(
|
||||
storageLocation.ID,
|
||||
storageLocation.Country,
|
||||
storageLocation.City,
|
||||
storageLocation.Province,
|
||||
storageLocation.CoverImage,
|
||||
storageInfo.ID.Count().As("total")).
|
||||
LeftJoin(storageInfo, storageInfo.LocationID.EqCol(storageLocation.ID)).
|
||||
Where(storageLocation.UserID.Eq(uid),
|
||||
LeftJoin(storageLocation, storageLocation.ID.EqCol(storageInfo.LocationID)).
|
||||
Where(storageInfo.UserID.Eq(uid),
|
||||
storageInfo.Provider.Eq(req.Provider),
|
||||
storageInfo.Bucket.Eq(req.Bucket)).
|
||||
storageInfo.Bucket.Eq(req.Bucket),
|
||||
storageInfo.LocationID.Neq(0)).
|
||||
Order(storageLocation.CreatedAt.Desc()).
|
||||
Group(storageLocation.ID).
|
||||
Scan(&locations)
|
||||
|
@@ -173,7 +173,7 @@ func (l *QueryRecentImageListLogic) QueryRecentImageList(req *types.RecentListRe
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
@@ -160,7 +160,7 @@ func (l *QueryThingDetailListLogic) QueryThingDetailList(req *types.ThingDetailL
|
||||
|
||||
// 缓存结果
|
||||
if data, err := json.Marshal(resp); err == nil {
|
||||
expireTime := 5*time.Minute + time.Duration(rand.Intn(300))*time.Second
|
||||
expireTime := 1*time.Minute + time.Duration(rand.Intn(60))*time.Second
|
||||
if err := l.svcCtx.RedisClient.Set(l.ctx, cacheKey, data, expireTime).Err(); err != nil {
|
||||
logx.Error("Failed to cache image list:", err)
|
||||
}
|
||||
|
379
app/auth/api/internal/logic/storage/search_image_logic.go
Normal file
379
app/auth/api/internal/logic/storage/search_image_logic.go
Normal file
@@ -0,0 +1,379 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"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"
|
||||
"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"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SearchImageLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewSearchImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SearchImageLogic {
|
||||
return &SearchImageLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *SearchImageLogic) SearchImage(req *types.SearchImageRequest) (resp *types.SearchImageResponse, err error) {
|
||||
uid, ok := l.ctx.Value("user_id").(string)
|
||||
if !ok {
|
||||
return nil, errors.New("user_id not found")
|
||||
}
|
||||
|
||||
baseQuery := map[string]interface{}{
|
||||
"query": map[string]interface{}{
|
||||
"bool": map[string]interface{}{
|
||||
"must": []map[string]interface{}{
|
||||
{"term": map[string]interface{}{"provider": req.Provider}},
|
||||
{"term": map[string]interface{}{"bucket": req.Bucket}},
|
||||
{"term": map[string]interface{}{"uid": uid}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
switch req.Type {
|
||||
case "time":
|
||||
// 时间范围查询(示例:"[2023-01-01,2023-12-31]")
|
||||
start, end, err := parseTimeRange(req.Keyword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("时间解析失败: %w", err)
|
||||
}
|
||||
addTimeRangeQuery(baseQuery, start, end)
|
||||
case "person":
|
||||
// 人脸ID精确匹配
|
||||
faceID, err := strconv.ParseInt(req.Keyword, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("人脸ID格式错误: %w", err)
|
||||
}
|
||||
addFaceIDQuery(baseQuery, faceID)
|
||||
case "thing":
|
||||
// 标签和分类匹配
|
||||
addThingQuery(baseQuery, req.Keyword)
|
||||
case "picture":
|
||||
// 图片属性匹配(示例:文件类型)
|
||||
addPictureQuery(baseQuery, req.Keyword)
|
||||
case "location":
|
||||
addLocationQuery(baseQuery, req.Keyword)
|
||||
default:
|
||||
return nil, errors.New("不支持的查询类型")
|
||||
}
|
||||
// 执行查询
|
||||
var target types.ZincFileInfo
|
||||
result, err := l.svcCtx.ZincClient.Search(constant.ZincIndexNameStorageInfo, baseQuery, target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询失败: %w", 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")
|
||||
}
|
||||
// 按日期分组处理
|
||||
groupedImages := sync.Map{}
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, hit := range result.Hits.Hits {
|
||||
wg.Add(1)
|
||||
go func(hit struct { // 明确传递 hit 结构
|
||||
ID string `json:"_id"`
|
||||
Source interface{} `json:"_source"`
|
||||
Score float64 `json:"_score"`
|
||||
}) {
|
||||
defer wg.Done()
|
||||
|
||||
// 类型断言转换
|
||||
source, err := convertToZincFileInfo(hit.Source)
|
||||
if err != nil {
|
||||
logx.Errorf("数据转换失败: %v | 原始数据: %+v", err, hit.Source)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成日期键(示例格式:2023年8月15日 星期二)
|
||||
weekday := WeekdayMap[source.CreatedAt.Weekday()]
|
||||
dateKey := source.CreatedAt.Format("2006年1月2日 星期" + weekday)
|
||||
|
||||
// 生成访问链接
|
||||
thumbnailUrl, err := service.PresignedURL(l.ctx, ossConfig.BucketName, source.ThumbPath, 15*time.Minute)
|
||||
if err != nil {
|
||||
logx.Errorf("生成缩略图链接失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fileUrl, err := service.PresignedURL(l.ctx, ossConfig.BucketName, source.FilePath, 15*time.Minute)
|
||||
if err != nil {
|
||||
logx.Errorf("生成文件链接失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建元数据
|
||||
meta := types.ImageMeta{
|
||||
ID: source.StorageId,
|
||||
FileName: source.FileName,
|
||||
URL: fileUrl,
|
||||
Width: source.ThumbW,
|
||||
Height: source.ThumbH,
|
||||
CreatedAt: source.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
Thumbnail: thumbnailUrl,
|
||||
}
|
||||
|
||||
// 线程安全写入 Map
|
||||
value, _ := groupedImages.LoadOrStore(dateKey, []types.ImageMeta{})
|
||||
images := value.([]types.ImageMeta)
|
||||
images = append(images, meta)
|
||||
groupedImages.Store(dateKey, images)
|
||||
}(struct {
|
||||
ID string `json:"_id"`
|
||||
Source interface{} `json:"_source"`
|
||||
Score float64 `json:"_score"`
|
||||
}(hit)) // 将 hit 作为参数传递
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 转换分组结果
|
||||
var records []types.AllImageDetail
|
||||
groupedImages.Range(func(key, value interface{}) bool {
|
||||
records = append(records, types.AllImageDetail{
|
||||
Date: key.(string),
|
||||
List: value.([]types.ImageMeta),
|
||||
})
|
||||
return true
|
||||
})
|
||||
// 按日期降序排序
|
||||
sort.Slice(records, func(i, j int) bool {
|
||||
ti, _ := time.Parse("2006年1月2日 星期一", records[i].Date)
|
||||
tj, _ := time.Parse("2006年1月2日 星期一", records[j].Date)
|
||||
return ti.After(tj)
|
||||
})
|
||||
return &types.SearchImageResponse{
|
||||
Records: records,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// 时间范围解析(支持日期和时间戳)
|
||||
func parseTimeRange(input string) (int64, int64, error) {
|
||||
input = strings.Trim(input, "[]")
|
||||
parts := strings.Split(input, ",")
|
||||
if len(parts) != 2 {
|
||||
return 0, 0, errors.New("时间格式错误")
|
||||
}
|
||||
|
||||
parseTime := func(s string) (int64, error) {
|
||||
// 尝试解析为日期格式
|
||||
if t, err := time.Parse("2006-01-02", strings.TrimSpace(s)); err == nil {
|
||||
return t.Unix(), nil
|
||||
}
|
||||
// 尝试解析为时间戳
|
||||
if ts, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return ts, nil
|
||||
}
|
||||
return 0, errors.New("无效时间格式")
|
||||
}
|
||||
|
||||
start, err := parseTime(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
end, err := parseTime(parts[1])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
func addTimeRangeQuery(query map[string]interface{}, start, end int64) {
|
||||
must := query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"]
|
||||
timeQuery := map[string]interface{}{
|
||||
"range": map[string]interface{}{
|
||||
"created_at": map[string]interface{}{ // 改为使用 created_at 字段
|
||||
"gte": start * 1000, // 转换为毫秒(根据格式决定)
|
||||
"lte": end * 1000,
|
||||
},
|
||||
},
|
||||
}
|
||||
query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"] = append(must.([]map[string]interface{}), timeQuery)
|
||||
}
|
||||
|
||||
// 修改后的标签查询
|
||||
func addThingQuery(query map[string]interface{}, keyword string) {
|
||||
must := query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"]
|
||||
tagQuery := map[string]interface{}{
|
||||
"multi_match": map[string]interface{}{
|
||||
"query": keyword,
|
||||
"fields": []string{"tag_name", "top_category"}, // 使用新字段名
|
||||
},
|
||||
}
|
||||
query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"] = append(must.([]map[string]interface{}), tagQuery)
|
||||
}
|
||||
|
||||
// 修改后的文件类型查询
|
||||
// 修改后的 picture 类型查询 (同时搜索文件名和文件类型)
|
||||
func addPictureQuery(query map[string]interface{}, keyword string) {
|
||||
must := query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"]
|
||||
|
||||
pictureQuery := map[string]interface{}{
|
||||
"bool": map[string]interface{}{
|
||||
"should": []map[string]interface{}{
|
||||
{
|
||||
"wildcard": map[string]interface{}{
|
||||
"file_name": "*" + strings.ToLower(keyword) + "*",
|
||||
},
|
||||
},
|
||||
{
|
||||
"term": map[string]interface{}{
|
||||
"file_type": strings.ToLower(keyword),
|
||||
},
|
||||
},
|
||||
},
|
||||
"minimum_should_match": 1,
|
||||
},
|
||||
}
|
||||
|
||||
query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"] = append(
|
||||
must.([]map[string]interface{}),
|
||||
pictureQuery,
|
||||
)
|
||||
}
|
||||
|
||||
// 添加人脸ID查询
|
||||
func addFaceIDQuery(query map[string]interface{}, faceID int64) {
|
||||
must := query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"]
|
||||
idQuery := map[string]interface{}{
|
||||
"term": map[string]interface{}{
|
||||
"face_id": faceID,
|
||||
},
|
||||
}
|
||||
query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"] = append(must.([]map[string]interface{}), idQuery)
|
||||
}
|
||||
|
||||
func addLocationQuery(query map[string]interface{}, keyword string) {
|
||||
must := query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"]
|
||||
|
||||
locationQuery := map[string]interface{}{
|
||||
"multi_match": map[string]interface{}{
|
||||
"query": keyword,
|
||||
"fields": []string{"country", "province", "city"},
|
||||
"type": "best_fields", // 优先匹配最多字段
|
||||
},
|
||||
}
|
||||
|
||||
query["query"].(map[string]interface{})["bool"].(map[string]interface{})["must"] = append(
|
||||
must.([]map[string]interface{}),
|
||||
locationQuery,
|
||||
)
|
||||
}
|
||||
|
||||
// ZincSearch 响应结构
|
||||
type ZincSearchResult struct {
|
||||
Hits struct {
|
||||
Total struct {
|
||||
Value int `json:"value"`
|
||||
} `json:"total"`
|
||||
Hits []struct {
|
||||
ID string `json:"_id"`
|
||||
Source types.FileUploadMessage `json:"_source"`
|
||||
Score float64 `json:"_score"`
|
||||
} `json:"hits"`
|
||||
} `json:"hits"`
|
||||
}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *SearchImageLogic) 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 *SearchImageLogic) getOssConfigFromCacheOrDb(cacheKey, uid, provider string) (*storageConfig.StorageConfig, error) {
|
||||
result, err := l.svcCtx.RedisClient.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return nil, errors.New("get oss config failed")
|
||||
}
|
||||
|
||||
var ossConfig *storageConfig.StorageConfig
|
||||
if result != "" {
|
||||
var redisOssConfig model.ScaStorageConfig
|
||||
if err = json.Unmarshal([]byte(result), &redisOssConfig); err != nil {
|
||||
return nil, errors.New("unmarshal oss config failed")
|
||||
}
|
||||
return l.decryptConfig(&redisOssConfig)
|
||||
}
|
||||
|
||||
// 缓存未命中,从数据库中加载
|
||||
scaOssConfig := l.svcCtx.DB.ScaStorageConfig
|
||||
dbOssConfig, err := scaOssConfig.Where(scaOssConfig.UserID.Eq(uid), scaOssConfig.Provider.Eq(provider)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存数据库配置
|
||||
ossConfig, err = l.decryptConfig(dbOssConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
marshalData, err := json.Marshal(dbOssConfig)
|
||||
if err != nil {
|
||||
return nil, errors.New("marshal oss config failed")
|
||||
}
|
||||
err = l.svcCtx.RedisClient.Set(l.ctx, cacheKey, marshalData, 0).Err()
|
||||
if err != nil {
|
||||
return nil, errors.New("set oss config failed")
|
||||
}
|
||||
|
||||
return ossConfig, nil
|
||||
}
|
||||
|
||||
// 新的数据转换函数
|
||||
func convertToZincFileInfo(source interface{}) (types.ZincFileInfo, error) {
|
||||
data, err := json.Marshal(source)
|
||||
if err != nil {
|
||||
return types.ZincFileInfo{}, fmt.Errorf("序列化失败: %w", err)
|
||||
}
|
||||
|
||||
var info types.ZincFileInfo
|
||||
if err := json.Unmarshal(data, &info); err != nil {
|
||||
return types.ZincFileInfo{}, fmt.Errorf("反序列化失败: %w", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
@@ -76,6 +76,12 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解析上传配置信息
|
||||
settingResult, err := l.parseUploadSettingResult(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 使用 `errgroup.Group` 处理并发任务
|
||||
var (
|
||||
faceId int64
|
||||
@@ -86,22 +92,24 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
// 创建信号量,限制最大并发上传数(比如最多同时 5 个任务)
|
||||
sem := semaphore.NewWeighted(5)
|
||||
|
||||
// 进行人脸识别
|
||||
g.Go(func() error {
|
||||
if result.FileType == "image/png" || result.FileType == "image/jpeg" {
|
||||
face, err := l.svcCtx.AiSvcRpc.FaceRecognition(l.ctx, &pb.FaceRecognitionRequest{
|
||||
Face: data,
|
||||
UserId: uid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
if settingResult.FaceDetection {
|
||||
// 进行人脸识别
|
||||
g.Go(func() error {
|
||||
if result.FileType == "image/png" || result.FileType == "image/jpeg" {
|
||||
face, err := l.svcCtx.AiSvcRpc.FaceRecognition(l.ctx, &pb.FaceRecognitionRequest{
|
||||
Face: data,
|
||||
UserId: uid,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if face != nil {
|
||||
faceId = face.GetFaceId()
|
||||
}
|
||||
}
|
||||
if face != nil {
|
||||
faceId = face.GetFaceId()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
// 上传文件到 OSS
|
||||
g.Go(func() error {
|
||||
if err := sem.Acquire(ctx, 1); err != nil {
|
||||
@@ -133,12 +141,13 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
|
||||
}
|
||||
|
||||
fileUploadMessage := &types.FileUploadMessage{
|
||||
UID: uid,
|
||||
Result: result,
|
||||
FaceID: faceId,
|
||||
FileHeader: header,
|
||||
FilePath: filePath,
|
||||
ThumbPath: thumbPath,
|
||||
UID: uid,
|
||||
Result: result,
|
||||
FaceID: faceId,
|
||||
FileName: header.Filename,
|
||||
FileSize: header.Size,
|
||||
FilePath: filePath,
|
||||
ThumbPath: thumbPath,
|
||||
}
|
||||
// 转换为 JSON
|
||||
messageData, err := json.Marshal(fileUploadMessage)
|
||||
@@ -189,6 +198,16 @@ func (l *UploadFileLogic) parseImageInfoResult(r *http.Request) (types.File, err
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 解析设置结果
|
||||
func (l *UploadFileLogic) parseUploadSettingResult(r *http.Request) (types.UploadSetting, error) {
|
||||
formValue := r.PostFormValue("setting")
|
||||
var result types.UploadSetting
|
||||
if err := json.Unmarshal([]byte(formValue), &result); err != nil {
|
||||
return result, errors.New("invalid result")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 上传文件到 OSS
|
||||
func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHeader, file multipart.File, thumbnail multipart.File, result types.File) (string, string, error) {
|
||||
cacheKey := constant.UserOssConfigPrefix + uid + ":" + result.Provider
|
||||
@@ -232,43 +251,6 @@ func (l *UploadFileLogic) uploadFileToOSS(uid string, header *multipart.FileHead
|
||||
return objectKey, thumbObjectKey, nil
|
||||
}
|
||||
|
||||
//func (l *UploadFileLogic) uploadFileToMinio(uid string, header *multipart.FileHeader, file multipart.File, result types.File) (string, error) {
|
||||
// objectKey := path.Join(
|
||||
// uid,
|
||||
// time.Now().Format("2006/01"), // 按年/月划分目录
|
||||
// l.classifyFile(result.FileType, result.IsScreenshot),
|
||||
// fmt.Sprintf("%s_%s%s", strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)), kgo.SimpleUuid(), filepath.Ext(header.Filename)),
|
||||
// )
|
||||
// exists, err := l.svcCtx.MinioClient.BucketExists(l.ctx, constant.ThumbnailBucketName)
|
||||
// if err != nil || !exists {
|
||||
// err = l.svcCtx.MinioClient.MakeBucket(l.ctx, constant.ThumbnailBucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true})
|
||||
// if err != nil {
|
||||
// logx.Errorf("Failed to create MinIO bucket: %v", err)
|
||||
// return "", err
|
||||
// }
|
||||
// }
|
||||
// // 上传到MinIO
|
||||
// _, err = l.svcCtx.MinioClient.PutObject(
|
||||
// l.ctx,
|
||||
// constant.ThumbnailBucketName,
|
||||
// objectKey,
|
||||
// file,
|
||||
// int64(result.ThumbSize),
|
||||
// minio.PutObjectOptions{
|
||||
// ContentType: result.FileType,
|
||||
// },
|
||||
// )
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// //reqParams := make(url.Values)
|
||||
// //presignedURL, err := l.svcCtx.MinioClient.PresignedGetObject(l.ctx, constant.ThumbnailBucketName, objectKey, time.Hour*24*7, reqParams)
|
||||
// //if err != nil {
|
||||
// // return "", "", err
|
||||
// //}
|
||||
// return objectKey, nil
|
||||
//}
|
||||
|
||||
// 提取解密操作为函数
|
||||
func (l *UploadFileLogic) decryptConfig(dbConfig *model.ScaStorageConfig) (*config.StorageConfig, error) {
|
||||
accessKey, err := encrypt.Decrypt(dbConfig.AccessKey, l.svcCtx.Config.Encrypt.Key)
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"gorm.io/gorm"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"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"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/nsqx"
|
||||
"schisandra-album-cloud-microservices/common/storage/config"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NsqImageProcessConsumer struct {
|
||||
@@ -60,7 +61,7 @@ func (c *NsqImageProcessConsumer) HandleMessage(msg *nsq.Message) error {
|
||||
}
|
||||
|
||||
// 将文件信息存入数据库
|
||||
storageId, err := c.saveFileInfoToDB(message.UID, message.Result.Bucket, message.Result.Provider, message.FileHeader, message.Result, message.FaceID, message.FilePath, locationId, message.Result.AlbumId)
|
||||
storageId, err := c.saveFileInfoToDB(message.UID, message.Result.Bucket, message.Result.Provider, message.FileName, message.FileSize, message.Result, message.FaceID, message.FilePath, locationId, message.Result.AlbumId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,6 +72,46 @@ func (c *NsqImageProcessConsumer) HandleMessage(msg *nsq.Message) error {
|
||||
// 删除缓存
|
||||
c.afterImageUpload(message.UID)
|
||||
|
||||
zincFileInfo := types.ZincFileInfo{
|
||||
FaceID: message.FaceID,
|
||||
FileName: message.FileName,
|
||||
FileSize: message.FileSize,
|
||||
UID: message.UID,
|
||||
FilePath: message.FilePath,
|
||||
ThumbPath: message.ThumbPath,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
StorageId: storageId,
|
||||
Provider: message.Result.Provider,
|
||||
Bucket: message.Result.Bucket,
|
||||
FileType: message.Result.FileType,
|
||||
IsAnime: message.Result.IsAnime,
|
||||
TagName: message.Result.TagName,
|
||||
Landscape: message.Result.Landscape,
|
||||
TopCategory: message.Result.TopCategory,
|
||||
IsScreenshot: message.Result.IsScreenshot,
|
||||
Width: message.Result.Width,
|
||||
Height: message.Result.Height,
|
||||
Longitude: message.Result.Longitude,
|
||||
Latitude: message.Result.Latitude,
|
||||
ThumbW: message.Result.ThumbW,
|
||||
ThumbH: message.Result.ThumbH,
|
||||
ThumbSize: message.Result.ThumbSize,
|
||||
AlbumId: message.Result.AlbumId,
|
||||
HasQrcode: message.Result.HasQrcode,
|
||||
Country: country,
|
||||
Province: province,
|
||||
City: city,
|
||||
}
|
||||
|
||||
err = c.svcCtx.ZincClient.CreateFileUploadIndex(constant.ZincIndexNameStorageInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.insertFileInfoTOZinc(constant.ZincIndexNameStorageInfo, storageId, zincFileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -225,7 +266,7 @@ func (c *NsqImageProcessConsumer) saveFileThumbnailInfoToDB(uid string, filePath
|
||||
}
|
||||
|
||||
// 将 EXIF 和文件信息存入数据库
|
||||
func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string, header *multipart.FileHeader, result types.File, faceId int64, filePath string, locationID, albumId int64) (int64, error) {
|
||||
func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string, fileName string, fileSize int64, result types.File, faceId int64, filePath string, locationID, albumId int64) (int64, error) {
|
||||
tx := c.svcCtx.DB.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -238,8 +279,8 @@ func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string,
|
||||
UserID: uid,
|
||||
Provider: provider,
|
||||
Bucket: bucket,
|
||||
FileName: header.Filename,
|
||||
FileSize: strconv.FormatInt(header.Size, 10),
|
||||
FileName: fileName,
|
||||
FileSize: strconv.FormatInt(fileSize, 10),
|
||||
FileType: result.FileType,
|
||||
Path: filePath,
|
||||
FaceID: faceId,
|
||||
@@ -261,6 +302,7 @@ func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string,
|
||||
Tag: result.TagName,
|
||||
IsAnime: strconv.FormatBool(result.IsAnime),
|
||||
Category: result.TopCategory,
|
||||
HasQrcode: strconv.FormatBool(result.HasQrcode),
|
||||
}
|
||||
err = tx.ScaStorageExtra.Create(scaStorageExtra)
|
||||
if err != nil {
|
||||
@@ -292,6 +334,22 @@ func (c *NsqImageProcessConsumer) afterImageUpload(uid string) {
|
||||
// 删除所有匹配的键
|
||||
if err := c.svcCtx.RedisClient.Del(c.ctx, keys...).Err(); err != nil {
|
||||
logx.Errorf("删除缓存键 %s 失败: %v", keyPattern, err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NsqImageProcessConsumer) insertFileInfoTOZinc(indexName string, docID int64, message types.ZincFileInfo) (int64, error) {
|
||||
url := fmt.Sprintf("%s/api/%s/_doc/%v", c.svcCtx.ZincClient.BaseURL, indexName, docID)
|
||||
resp, err := c.svcCtx.ZincClient.Client.R().
|
||||
SetBasicAuth(c.svcCtx.ZincClient.Username, c.svcCtx.ZincClient.Password).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(message).
|
||||
Put(url)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("请求失败: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return 0, fmt.Errorf("插入失败 (状态码 %d): %s", resp.StatusCode(), resp.String())
|
||||
}
|
||||
return docID, nil
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ import (
|
||||
"schisandra-album-cloud-microservices/common/storage"
|
||||
"schisandra-album-cloud-microservices/common/storage/manager"
|
||||
"schisandra-album-cloud-microservices/common/wechat_official"
|
||||
"schisandra-album-cloud-microservices/common/zincx"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
@@ -48,6 +49,7 @@ type ServiceContext struct {
|
||||
MinioClient *minio.Client
|
||||
GeoRegionData *geo_json.RegionData
|
||||
NSQProducer *nsq.Producer
|
||||
ZincClient *zincx.ZincClient
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
@@ -72,6 +74,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
||||
MinioClient: miniox.NewMinio(c.Minio.Endpoint, c.Minio.AccessKeyID, c.Minio.SecretAccessKey, c.Minio.UseSSL),
|
||||
GeoRegionData: geo_json.NewGeoJSON(),
|
||||
NSQProducer: nsqx.NewNsqProducer(c.NSQ.NSQDHost),
|
||||
ZincClient: zincx.NewZincClient(c.Zinc.URL, c.Zinc.Username, c.Zinc.Password),
|
||||
}
|
||||
return serviceContext
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -23,18 +22,28 @@ type File struct {
|
||||
ThumbH float64 `json:"thumb_h"`
|
||||
ThumbSize float64 `json:"thumb_size"`
|
||||
AlbumId int64 `json:"albumId"`
|
||||
HasQrcode bool `json:"hasQrcode"`
|
||||
}
|
||||
|
||||
type UploadSetting struct {
|
||||
NsfwDetection bool `json:"nsfw_detection"`
|
||||
AnimeDetection bool `json:"anime_detection"`
|
||||
LandscapeDetection bool `json:"landscape_detection"`
|
||||
ScreenshotDetection bool `json:"screenshot_detection"`
|
||||
GpsDetection bool `json:"gps_detection"`
|
||||
TargetDetection bool `json:"target_detection"`
|
||||
QrcodeDetection bool `json:"qrcode_detection"`
|
||||
FaceDetection bool `json:"face_detection"`
|
||||
}
|
||||
|
||||
// FileUploadMessage represents a message sent to the user after a file upload.
|
||||
type FileUploadMessage struct {
|
||||
FaceID int64 `json:"face_id"`
|
||||
FileHeader *multipart.FileHeader `json:"fileHeader"`
|
||||
Result File `json:"result"`
|
||||
UID string `json:"uid"`
|
||||
FilePath string `json:"filePath"`
|
||||
URL string `json:"url"`
|
||||
ThumbPath string `json:"thumbPath"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
FaceID int64 `json:"face_id"`
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
Result File `json:"result"`
|
||||
UID string `json:"uid"`
|
||||
FilePath string `json:"file_path"`
|
||||
ThumbPath string `json:"thumb_path"`
|
||||
}
|
||||
|
||||
type FileInfoResult struct {
|
||||
@@ -74,3 +83,34 @@ type LocationInfo struct {
|
||||
CoverImage string `json:"cover_image"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type ZincFileInfo struct {
|
||||
FaceID int64 `json:"face_id"`
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
UID string `json:"uid"`
|
||||
FilePath string `json:"file_path"`
|
||||
ThumbPath string `json:"thumb_path"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
StorageId int64 `json:"storage_id"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
FileType string `json:"file_type"`
|
||||
IsAnime bool `json:"is_anime"`
|
||||
TagName string `json:"tag_name"`
|
||||
Landscape string `json:"landscape"`
|
||||
TopCategory string `json:"top_category"`
|
||||
IsScreenshot bool `json:"is_screenshot"`
|
||||
Width float64 `json:"width"`
|
||||
Height float64 `json:"height"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
ThumbW float64 `json:"thumb_w"`
|
||||
ThumbH float64 `json:"thumb_h"`
|
||||
ThumbSize float64 `json:"thumb_size"`
|
||||
AlbumId int64 `json:"album_id"`
|
||||
HasQrcode bool `json:"has_qrcode"`
|
||||
Country string `json:"country"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
}
|
||||
|
@@ -303,7 +303,8 @@ type QueryShareImageResponse struct {
|
||||
}
|
||||
|
||||
type QueryShareInfoRequest struct {
|
||||
InviteCode string `json:"invite_code"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
AccessPassword string `json:"access_password,omitempty"`
|
||||
}
|
||||
|
||||
type RecentListRequest struct {
|
||||
@@ -363,6 +364,18 @@ type RotateCaptchaResponse struct {
|
||||
Thumb string `json:"thumb"`
|
||||
}
|
||||
|
||||
type SearchImageRequest struct {
|
||||
Type string `json:"type"`
|
||||
Keyword string `json:"keyword"`
|
||||
Provider string `json:"provider"`
|
||||
Bucket string `json:"bucket"`
|
||||
InputImage string `json:"input_image,omitempty"`
|
||||
}
|
||||
|
||||
type SearchImageResponse struct {
|
||||
Records []AllImageDetail `json:"records"`
|
||||
}
|
||||
|
||||
type ShareAlbumRequest struct {
|
||||
ID int64 `json:"id"`
|
||||
ExpireDate string `json:"expire_date"`
|
||||
@@ -402,6 +415,8 @@ type ShareInfoResponse struct {
|
||||
SharerAvatar string `json:"sharer_avatar"`
|
||||
SharerName string `json:"sharer_name"`
|
||||
AlbumName string `json:"album_name"`
|
||||
InviteCode string `json:"invite_code"`
|
||||
SharerUID string `json:"sharer_uid"`
|
||||
}
|
||||
|
||||
type ShareOverviewResponse struct {
|
||||
|
@@ -22,6 +22,7 @@ type ScaStorageExtra struct {
|
||||
IsAnime string `gorm:"column:is_anime;type:varchar(50);comment:是否是动漫图片" json:"is_anime"` // 是否是动漫图片
|
||||
Landscape string `gorm:"column:landscape;type:varchar(50);comment:风景类型" json:"landscape"` // 风景类型
|
||||
Hash string `gorm:"column:hash;type:varchar(255);comment:哈希值" json:"hash"` // 哈希值
|
||||
HasQrcode string `gorm:"column:has_qrcode;type:varchar(50);comment:是否有二维码" json:"has_qrcode"` // 是否有二维码
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;autoCreateTime;comment:创建时间" json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;autoUpdateTime;comment:更新时间" json:"updated_at"` // 更新时间
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||
|
@@ -35,6 +35,7 @@ func newScaStorageExtra(db *gorm.DB, opts ...gen.DOOption) scaStorageExtra {
|
||||
_scaStorageExtra.IsAnime = field.NewString(tableName, "is_anime")
|
||||
_scaStorageExtra.Landscape = field.NewString(tableName, "landscape")
|
||||
_scaStorageExtra.Hash = field.NewString(tableName, "hash")
|
||||
_scaStorageExtra.HasQrcode = field.NewString(tableName, "has_qrcode")
|
||||
_scaStorageExtra.CreatedAt = field.NewTime(tableName, "created_at")
|
||||
_scaStorageExtra.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||
_scaStorageExtra.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
@@ -57,6 +58,7 @@ type scaStorageExtra struct {
|
||||
IsAnime field.String // 是否是动漫图片
|
||||
Landscape field.String // 风景类型
|
||||
Hash field.String // 哈希值
|
||||
HasQrcode field.String // 是否有二维码
|
||||
CreatedAt field.Time // 创建时间
|
||||
UpdatedAt field.Time // 更新时间
|
||||
DeletedAt field.Field // 删除时间
|
||||
@@ -84,6 +86,7 @@ func (s *scaStorageExtra) updateTableName(table string) *scaStorageExtra {
|
||||
s.IsAnime = field.NewString(table, "is_anime")
|
||||
s.Landscape = field.NewString(table, "landscape")
|
||||
s.Hash = field.NewString(table, "hash")
|
||||
s.HasQrcode = field.NewString(table, "has_qrcode")
|
||||
s.CreatedAt = field.NewTime(table, "created_at")
|
||||
s.UpdatedAt = field.NewTime(table, "updated_at")
|
||||
s.DeletedAt = field.NewField(table, "deleted_at")
|
||||
@@ -103,7 +106,7 @@ func (s *scaStorageExtra) GetFieldByName(fieldName string) (field.OrderExpr, boo
|
||||
}
|
||||
|
||||
func (s *scaStorageExtra) fillFieldMap() {
|
||||
s.fieldMap = make(map[string]field.Expr, 11)
|
||||
s.fieldMap = make(map[string]field.Expr, 12)
|
||||
s.fieldMap["id"] = s.ID
|
||||
s.fieldMap["user_id"] = s.UserID
|
||||
s.fieldMap["info_id"] = s.InfoID
|
||||
@@ -112,6 +115,7 @@ func (s *scaStorageExtra) fillFieldMap() {
|
||||
s.fieldMap["is_anime"] = s.IsAnime
|
||||
s.fieldMap["landscape"] = s.Landscape
|
||||
s.fieldMap["hash"] = s.Hash
|
||||
s.fieldMap["has_qrcode"] = s.HasQrcode
|
||||
s.fieldMap["created_at"] = s.CreatedAt
|
||||
s.fieldMap["updated_at"] = s.UpdatedAt
|
||||
s.fieldMap["deleted_at"] = s.DeletedAt
|
||||
|
Reference in New Issue
Block a user