✨ 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
|
||||
|
5
common/constant/zinc_index_name.go
Normal file
5
common/constant/zinc_index_name.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ZincIndexNameStorageInfo = "storage_info"
|
||||
)
|
78
common/zincx/special.go
Normal file
78
common/zincx/special.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package zincx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 创建索引(幂等操作,若存在则跳过)
|
||||
func (zc *ZincClient) CreateFileUploadIndex(indexName string) error {
|
||||
exists, err := zc.IndexExists(indexName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查索引失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil // 索引已存在则跳过
|
||||
}
|
||||
|
||||
// 定义完整的索引映射
|
||||
mapping := map[string]interface{}{
|
||||
"mappings": map[string]interface{}{
|
||||
"properties": map[string]interface{}{
|
||||
// 基础信息
|
||||
"storage_id": map[string]string{"type": "numeric"},
|
||||
"face_id": map[string]string{"type": "numeric"},
|
||||
"file_name": map[string]string{"type": "keyword"},
|
||||
"file_size": map[string]string{"type": "numeric"},
|
||||
"uid": map[string]string{"type": "keyword"},
|
||||
"file_path": map[string]string{"type": "text"},
|
||||
"thumb_path": map[string]string{"type": "text"},
|
||||
"created_at": map[string]string{
|
||||
"type": "date"},
|
||||
|
||||
// 文件元数据
|
||||
"provider": map[string]string{"type": "keyword"},
|
||||
"bucket": map[string]string{"type": "keyword"},
|
||||
"file_type": map[string]string{"type": "keyword"},
|
||||
"is_anime": map[string]string{"type": "boolean"},
|
||||
"tag_name": map[string]string{"type": "keyword"},
|
||||
"landscape": map[string]string{"type": "keyword"},
|
||||
"top_category": map[string]string{"type": "keyword"},
|
||||
"is_screenshot": map[string]string{"type": "boolean"},
|
||||
|
||||
// 媒体属性
|
||||
"width": map[string]string{"type": "numeric"},
|
||||
"height": map[string]string{"type": "numeric"},
|
||||
"thumb_w": map[string]string{"type": "numeric"},
|
||||
"thumb_h": map[string]string{"type": "numeric"},
|
||||
"thumb_size": map[string]string{"type": "numeric"},
|
||||
|
||||
// 地理信息
|
||||
"longitude": map[string]string{"type": "numeric"},
|
||||
"latitude": map[string]string{"type": "numeric"},
|
||||
"country": map[string]string{"type": "keyword"},
|
||||
"province": map[string]string{"type": "keyword"},
|
||||
"city": map[string]string{"type": "keyword"},
|
||||
|
||||
// 其他
|
||||
"album_id": map[string]string{"type": "numeric"},
|
||||
"has_qrcode": map[string]string{"type": "boolean"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(mapping).
|
||||
Put(zc.BaseURL + "/api/index/" + indexName)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建索引请求失败: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return fmt.Errorf("创建索引失败 (状态码 %d): %s", resp.StatusCode(), resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
201
common/zincx/zincx.go
Normal file
201
common/zincx/zincx.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package zincx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ZincClient struct {
|
||||
Client *resty.Client
|
||||
BaseURL string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewZincClient(BaseURL, Username, Password string) *ZincClient {
|
||||
Client := resty.New().
|
||||
SetDebug(false).
|
||||
SetDisableWarn(true)
|
||||
return &ZincClient{
|
||||
Client: Client,
|
||||
BaseURL: BaseURL,
|
||||
Username: Username,
|
||||
Password: Password,
|
||||
}
|
||||
}
|
||||
|
||||
// 检查索引是否存在 (内部方法)
|
||||
func (zc *ZincClient) IndexExists(indexName string) (bool, error) {
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
Head(zc.BaseURL + "/api/index/" + indexName)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return resp.StatusCode() == http.StatusOK, nil
|
||||
}
|
||||
|
||||
type IndexMapping struct {
|
||||
Mappings struct {
|
||||
Properties map[string]interface{} `json:"properties"`
|
||||
} `json:"mappings"`
|
||||
}
|
||||
|
||||
func (zc *ZincClient) CreateIndex(indexName string, mapping *IndexMapping) error {
|
||||
url := fmt.Sprintf("%s/api/index/%s", zc.BaseURL, indexName)
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(mapping).
|
||||
Put(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("创建索引失败: %s", resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zc *ZincClient) IndexDocument(indexName, documentID string, doc interface{}) error {
|
||||
url := fmt.Sprintf("%s/api/%s/_doc/%s", zc.BaseURL, indexName, documentID)
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(doc).
|
||||
Put(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("插入文档失败: %s", resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SearchRequest struct {
|
||||
Query struct {
|
||||
Match map[string]interface{} `json:"match"`
|
||||
} `json:"query"`
|
||||
}
|
||||
|
||||
// 泛型响应结构
|
||||
type ZincSearchResponse[T any] struct {
|
||||
Hits struct {
|
||||
Total struct {
|
||||
Value int `json:"value"`
|
||||
} `json:"total"`
|
||||
Hits []struct {
|
||||
ID string `json:"_id"`
|
||||
Source T `json:"_source"`
|
||||
Score float64 `json:"_score"`
|
||||
} `json:"hits"`
|
||||
} `json:"hits"`
|
||||
}
|
||||
|
||||
// 修改 Search 方法签名
|
||||
func (zc *ZincClient) Search(indexName string, query interface{}, resultType interface{}) (*ZincSearchResponse[interface{}], error) {
|
||||
url := fmt.Sprintf("%s/api/%s/_search", zc.BaseURL, indexName)
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(query).
|
||||
SetResult(&ZincSearchResponse[interface{}]{}).
|
||||
Post(url)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("搜索失败: %s", resp.String())
|
||||
}
|
||||
|
||||
// 手动反序列化以支持动态类型
|
||||
var rawResponse ZincSearchResponse[interface{}]
|
||||
if err := json.Unmarshal(resp.Body(), &rawResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将 Source 转换为目标类型
|
||||
for i := range rawResponse.Hits.Hits {
|
||||
data, _ := json.Marshal(rawResponse.Hits.Hits[i].Source)
|
||||
_ = json.Unmarshal(data, &resultType)
|
||||
rawResponse.Hits.Hits[i].Source = resultType
|
||||
}
|
||||
|
||||
return &rawResponse, nil
|
||||
}
|
||||
|
||||
type BulkRequest struct {
|
||||
Index string `json:"index"`
|
||||
ID string `json:"id"`
|
||||
Source interface{} `json:"source"`
|
||||
}
|
||||
|
||||
func (zc *ZincClient) BulkIndex(indexName string, docs []BulkRequest) error {
|
||||
url := fmt.Sprintf("%s/api/_bulk", zc.BaseURL)
|
||||
body := ""
|
||||
for _, doc := range docs {
|
||||
action := fmt.Sprintf(`{ "index": { "_index": "%s", "_id": "%s" } }`, indexName, doc.ID)
|
||||
source, _ := json.Marshal(doc.Source)
|
||||
body += action + "\n" + string(source) + "\n"
|
||||
}
|
||||
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetBody(body).
|
||||
Post(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("批量操作失败: %s", resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除文档
|
||||
func (zc *ZincClient) DeleteDocument(indexName, documentID string) error {
|
||||
url := fmt.Sprintf("%s/api/%s/_doc/%s", zc.BaseURL, indexName, documentID)
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
Delete(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("删除文档失败: %s", resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除索引
|
||||
func (zc *ZincClient) DeleteIndex(indexName string) error {
|
||||
url := fmt.Sprintf("%s/api/index/%s", zc.BaseURL, indexName)
|
||||
resp, err := zc.Client.R().
|
||||
SetBasicAuth(zc.Username, zc.Password).
|
||||
Delete(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("删除索引失败: %s", resp.String())
|
||||
}
|
||||
return nil
|
||||
}
|
1
go.mod
1
go.mod
@@ -77,6 +77,7 @@ require (
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-playground/assert/v2 v2.2.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.16.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
|
57
go.sum
57
go.sum
@@ -93,8 +93,10 @@ github.com/duke-git/lancet/v2 v2.3.4 h1:8XGI7P9w+/GqmEBEXYaH/XuNiM0f4/90Ioti0IvY
|
||||
github.com/duke-git/lancet/v2 v2.3.4/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/elastic/elastic-transport-go/v8 v8.6.1 h1:h2jQRqH6eLGiBSN4eZbQnJLtL4bC5b4lfVFRjw2R4e4=
|
||||
github.com/elastic/elastic-transport-go/v8 v8.6.1/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
|
||||
github.com/elastic/go-elasticsearch/v8 v8.17.1 h1:bOXChDoCMB4TIwwGqKd031U8OXssmWLT3UrAr9EGs3Q=
|
||||
github.com/elastic/go-elasticsearch/v8 v8.17.1/go.mod h1:MVJCtL+gJJ7x5jFeUmA20O7rvipX8GcQmo5iBcmaJn4=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
@@ -105,7 +107,6 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -121,12 +122,11 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
@@ -160,8 +160,6 @@ github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcb
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
@@ -181,7 +179,6 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
@@ -222,13 +219,9 @@ github.com/karlseguin/ccache/v3 v3.0.6/go.mod h1:b0qfdUOHl4vJgKFQN41paXIdBb3acAt
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -264,10 +257,6 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY
|
||||
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.85 h1:9psTLS/NTvC3MWoyjhjXpwcKoNbkongaCSF3PNpSuXo=
|
||||
github.com/minio/minio-go/v7 v7.0.85/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
|
||||
github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k=
|
||||
github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4=
|
||||
github.com/minio/minio-go/v7 v7.0.87 h1:nkr9x0u53PespfxfUqxP3UYWiE2a41gaofgNnC4Y8WQ=
|
||||
github.com/minio/minio-go/v7 v7.0.87/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
@@ -325,8 +314,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
|
||||
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
@@ -337,12 +324,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
||||
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
||||
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
|
||||
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
@@ -374,17 +357,11 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.61 h1:tKNIjvsezkdtajqE887XAw1VL8Pq1HNtpc7rfgz25lA=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.61/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.62 h1:7SZVCc31rkvMxod8nwvG1Ko0N5npT39/s3NhpHBvs70=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.62/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/wenlng/go-captcha-assets v1.0.1 h1:AdjRFMKmadPRWRTv0XEYfjDvcaayZ2yExITDvlK/7bk=
|
||||
github.com/wenlng/go-captcha-assets v1.0.1/go.mod h1:yQqc7rRbxgLCg+tWtVp+7Y317D1wIZDan/yIwt8wSac=
|
||||
github.com/wenlng/go-captcha-assets v1.0.5 h1:TL+31Qe/kJwcuYyU+jHedjSTZnMu1XKgktKL++lH9Js=
|
||||
github.com/wenlng/go-captcha-assets v1.0.5/go.mod h1:zinRACsdYcL/S6pHgI9Iv7FKTU41d00+43pNX+b9+MM=
|
||||
github.com/wenlng/go-captcha/v2 v2.0.2 h1:8twz6pI6xZwPvEGFezoFX395oFso1MuOlJt/tLiv7pk=
|
||||
github.com/wenlng/go-captcha/v2 v2.0.2/go.mod h1:5hac1em3uXoyC5ipZ0xFv9umNM/waQvYAQdr0cx/h34=
|
||||
github.com/wenlng/go-captcha/v2 v2.0.3 h1:QTZ39/gVDisPSgvL9O2X2HbTuj5P/z8QsdGB/aayg9c=
|
||||
github.com/wenlng/go-captcha/v2 v2.0.3/go.mod h1:5hac1em3uXoyC5ipZ0xFv9umNM/waQvYAQdr0cx/h34=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
@@ -414,8 +391,6 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.18/go.mod h1:BxVf2o5wXG9ZJV+/Cu7QNUiJYk4A29sA
|
||||
go.etcd.io/etcd/client/v3 v3.5.18 h1:nvvYmNHGumkDjZhTHgVU36A9pykGa2K4lAJ0yY7hcXA=
|
||||
go.etcd.io/etcd/client/v3 v3.5.18/go.mod h1:kmemwOsPU9broExyhYsBxX4spCTDX3yLgPMWtpBXG6E=
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
|
||||
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
@@ -468,16 +443,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
||||
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
|
||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg=
|
||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
@@ -511,8 +478,6 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -587,16 +552,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250212204824-5a70512c5d8b h1:i+d0RZa8Hs2L/MuaOQYI+krthcxdEbEM2N+Tf3kJ4zk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 h1:35ZFtrCgaAjF7AFAK0+lRSf+4AyYnWRbH7og13p7rZ4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:W9ynFDP/shebLB1Hl/ESTOap2jHd6pmLXPNZC7SVDbA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99 h1:ilJhrCga0AptpJZXmUYG4MCrx/zf3l1okuYz7YK9PPw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250224174004-546df14abb99/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99 h1:ZSlhAUqC4r8TPzqLXQ0m3upBNZeF+Y8jQ3c4CR3Ujms=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250224174004-546df14abb99/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
@@ -677,8 +634,6 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
|
||||
modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
|
||||
modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=
|
||||
modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
|
Reference in New Issue
Block a user