diff --git a/app/auth/api/auth.api b/app/auth/api/auth.api index b6d7f29..6fddd8e 100644 --- a/app/auth/api/auth.api +++ b/app/auth/api/auth.api @@ -40,7 +40,7 @@ type ( AccessToken string `json:"access_token"` ExpireAt int64 `json:"expire_at"` UID string `json:"uid"` - Username string `json:"username,omitempty"` + Username string `json:"username,optional"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` Status int64 `json:"status"` @@ -316,15 +316,15 @@ type ( CommentContent { NickName string `json:"nickname"` Avatar string `json:"avatar"` - Level int64 `json:"level,omitempty" default:"0"` + Level int64 `json:"level,optional" default:"0"` Id int64 `json:"id"` UserId string `json:"user_id"` TopicId string `json:"topic_id"` Content string `json:"content"` - ReplyTo int64 `json:"reply_to,omitempty"` - ReplyId int64 `json:"reply_id,omitempty"` - ReplyUser string `json:"reply_user,omitempty"` - ReplyNickname string `json:"reply_nickname,omitempty"` + ReplyTo int64 `json:"reply_to,optional"` + ReplyId int64 `json:"reply_id,optional"` + ReplyUser string `json:"reply_user,optional"` + ReplyNickname string `json:"reply_nickname,optional"` IsAuthor int64 `json:"is_author"` Likes int64 `json:"likes"` ReplyCount int64 `json:"reply_count"` @@ -333,7 +333,7 @@ type ( Browser string `json:"browser"` OperatingSystem string `json:"operating_system"` IsLiked bool `json:"is_liked" default:"false"` - Images string `json:"images,omitempty"` + Images string `json:"images,optional"` } // CommentListPageResponse 评论返回值 CommentListPageResponse { @@ -353,9 +353,9 @@ type ( Browser string `json:"browser"` OperatingSystem string `json:"operating_system"` CreatedTime string `json:"created_time"` - ReplyId int64 `json:"reply_id,omitempty"` - ReplyUser string `json:"reply_user,omitempty"` - ReplyTo int64 `json:"reply_to,omitempty"` + ReplyId int64 `json:"reply_id,optional"` + ReplyUser string `json:"reply_user,optional"` + ReplyTo int64 `json:"reply_to,optional"` } ) @@ -482,7 +482,7 @@ type ( } // 相册列表请求参数 AlbumListRequest { - Type int64 `json:"type,omitempty"` + Type int64 `json:"type,optional"` Sort bool `json:"sort"` } // 相册列表响应参数 @@ -649,8 +649,8 @@ type ( ShareAlbumRequest { ID int64 `json:"id"` ExpireDate string `json:"expire_date"` - AccessLimit int64 `json:"access_limit,omitempty"` - AccessPassword string `json:"access_password,omitempty"` + AccessLimit int64 `json:"access_limit,optional"` + AccessPassword string `json:"access_password,optional"` Provider string `json:"provider"` Bucket string `json:"bucket"` } @@ -705,6 +705,28 @@ type ( Provider string `json:"provider"` Bucket string `json:"bucket"` } + HeatmapMeta { + Date string `json:"date"` + Count int64 `json:"count"` + } + UserUploadInfoResponse { + ImageCount int64 `json:"image_count"` + TodayUploadCount int64 `json:"today_upload_count"` + ShareCount int64 `json:"share_count"` + TodayShareCount int64 `json:"today_share_count"` + FileSizeCount int64 `json:"file_size_count"` + TodayFileSizeCount int64 `json:"today_file_size_count"` + Heatmap []HeatmapMeta `json:"heatmap"` + } + ShareRecentMeta { + Date string `json:"date"` + VisitCount int64 `json:"visit_count"` + VisitorCount int64 `json:"visitor_count"` + PublishCount int64 `json:"publish_count"` + } + ShareRecentInfoResponse { + Records []ShareRecentMeta `json:"records"` + } ) // 文件上传 @@ -835,6 +857,14 @@ service auth { // 删除存储配置 @handler deleteStorageConfig post /config/delete (DeleteStorageConfigRequest) returns (string) + + // 获取用户上传统计信息 + @handler getUserUploadInfo + post /user/upload/info returns (UserUploadInfoResponse) + + // 获取分享最近的数据统计 + @handler getShareRecentInfo + post /share/recent/info returns (ShareRecentInfoResponse) } type ( @@ -848,15 +878,15 @@ type ( ThumbSize int64 `json:"thumb_size"` } ShareImageRequest { - Title string `json:"title,omitempty"` + Title string `json:"title,optional"` ExpireDate string `json:"expire_date"` - AccessLimit int64 `json:"access_limit,omitempty"` - AccessPassword string `json:"access_password,omitempty"` + AccessLimit int64 `json:"access_limit,optional"` + AccessPassword string `json:"access_password,optional"` Images []ShareImageMeta `json:"images"` } QueryShareImageRequest { InviteCode string `json:"invite_code"` - AccessPassword string `json:"access_password,omitempty"` + AccessPassword string `json:"access_password,optional"` } QueryShareImageResponse { Records []AllImageDetail `json:"records"` @@ -880,7 +910,7 @@ type ( } QueryShareInfoRequest { InviteCode string `json:"invite_code"` - AccessPassword string `json:"access_password,omitempty"` + AccessPassword string `json:"access_password,optional"` } ShareInfoResponse { ID int64 `json:"id"` @@ -951,3 +981,33 @@ service auth { post /record/delete (DeleteShareRecordRequest) returns (string) } +type ( + UserSecuritySettingResponse { + bindPhone bool `json:"bind_phone,default=false"` + bindEmail bool `json:"bind_email,default=falsel"` + bindWechat bool `json:"bind_wechat,default=false"` + bindQQ bool `json:"bind_qq,default=false"` + bindGitHub bool `json:"bind_github,default=false"` + bindGitee bool `json:"bind_gitee,default=false"` + setPassword bool `json:"set_password,default=false"` + } +) + +// 分享服务 +@server ( + group: auth // 微服务分组 + prefix: /api/auth/user // 微服务前缀 + timeout: 10s // 超时时间 + maxBytes: 104857600 // 最大请求大小 + signature: false // 是否开启签名验证 + middleware: SecurityHeadersMiddleware,CasbinVerifyMiddleware,NonceMiddleware // 注册中间件 + MaxConns: true // 是否开启最大连接数限制 + Recover: true // 是否开启自动恢复 + jwt: Auth // 是否开启jwt验证 +) +service auth { + // 判断用户安全设置 + @handler checkUserSecuritySetting + post /check/security/setting returns (UserSecuritySettingResponse) +} + diff --git a/app/auth/api/internal/handler/auth/check_user_security_setting_handler.go b/app/auth/api/internal/handler/auth/check_user_security_setting_handler.go new file mode 100644 index 0000000..d44234d --- /dev/null +++ b/app/auth/api/internal/handler/auth/check_user_security_setting_handler.go @@ -0,0 +1,21 @@ +package auth + +import ( + "net/http" + + "schisandra-album-cloud-microservices/app/auth/api/internal/logic/auth" + "schisandra-album-cloud-microservices/app/auth/api/internal/svc" + "schisandra-album-cloud-microservices/common/xhttp" +) + +func CheckUserSecuritySettingHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := auth.NewCheckUserSecuritySettingLogic(r.Context(), svcCtx) + resp, err := l.CheckUserSecuritySetting() + if err != nil { + xhttp.JsonBaseResponseCtx(r.Context(), w, err) + } else { + xhttp.JsonBaseResponseCtx(r.Context(), w, resp) + } + } +} diff --git a/app/auth/api/internal/handler/routes.go b/app/auth/api/internal/handler/routes.go index 2f63f1c..915386e 100644 --- a/app/auth/api/internal/handler/routes.go +++ b/app/auth/api/internal/handler/routes.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + auth "schisandra-album-cloud-microservices/app/auth/api/internal/handler/auth" captcha "schisandra-album-cloud-microservices/app/auth/api/internal/handler/captcha" client "schisandra-album-cloud-microservices/app/auth/api/internal/handler/client" comment "schisandra-album-cloud-microservices/app/auth/api/internal/handler/comment" @@ -24,6 +25,23 @@ import ( ) func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + rest.WithMiddlewares( + []rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.CasbinVerifyMiddleware, serverCtx.NonceMiddleware}, + []rest.Route{ + { + Method: http.MethodPost, + Path: "/check/security/setting", + Handler: auth.CheckUserSecuritySettingHandler(serverCtx), + }, + }..., + ), + rest.WithJwt(serverCtx.Config.Auth.AccessSecret), + rest.WithPrefix("/api/auth/user"), + rest.WithTimeout(10000*time.Millisecond), + rest.WithMaxBytes(104857600), + ) + server.AddRoutes( rest.WithMiddlewares( []rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware}, @@ -387,6 +405,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/image/url/single", Handler: storage.GetImageUrlHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/share/recent/info", + Handler: storage.GetShareRecentInfoHandler(serverCtx), + }, { Method: http.MethodPost, Path: "/uploads", @@ -402,6 +425,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/user/storage/list", Handler: storage.ListUserStorageHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/user/upload/info", + Handler: storage.GetUserUploadInfoHandler(serverCtx), + }, }..., ), rest.WithJwt(serverCtx.Config.Auth.AccessSecret), diff --git a/app/auth/api/internal/handler/storage/get_share_recent_info_handler.go b/app/auth/api/internal/handler/storage/get_share_recent_info_handler.go new file mode 100644 index 0000000..8765fb2 --- /dev/null +++ b/app/auth/api/internal/handler/storage/get_share_recent_info_handler.go @@ -0,0 +1,21 @@ +package storage + +import ( + "net/http" + + "schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage" + "schisandra-album-cloud-microservices/app/auth/api/internal/svc" + "schisandra-album-cloud-microservices/common/xhttp" +) + +func GetShareRecentInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := storage.NewGetShareRecentInfoLogic(r.Context(), svcCtx) + resp, err := l.GetShareRecentInfo() + if err != nil { + xhttp.JsonBaseResponseCtx(r.Context(), w, err) + } else { + xhttp.JsonBaseResponseCtx(r.Context(), w, resp) + } + } +} diff --git a/app/auth/api/internal/handler/storage/get_user_upload_info_handler.go b/app/auth/api/internal/handler/storage/get_user_upload_info_handler.go new file mode 100644 index 0000000..f7d974a --- /dev/null +++ b/app/auth/api/internal/handler/storage/get_user_upload_info_handler.go @@ -0,0 +1,21 @@ +package storage + +import ( + "net/http" + + "schisandra-album-cloud-microservices/app/auth/api/internal/logic/storage" + "schisandra-album-cloud-microservices/app/auth/api/internal/svc" + "schisandra-album-cloud-microservices/common/xhttp" +) + +func GetUserUploadInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + l := storage.NewGetUserUploadInfoLogic(r.Context(), svcCtx) + resp, err := l.GetUserUploadInfo() + if err != nil { + xhttp.JsonBaseResponseCtx(r.Context(), w, err) + } else { + xhttp.JsonBaseResponseCtx(r.Context(), w, resp) + } + } +} diff --git a/app/auth/api/internal/logic/auth/check_user_security_setting_logic.go b/app/auth/api/internal/logic/auth/check_user_security_setting_logic.go new file mode 100644 index 0000000..131eae3 --- /dev/null +++ b/app/auth/api/internal/logic/auth/check_user_security_setting_logic.go @@ -0,0 +1,64 @@ +package auth + +import ( + "context" + "errors" + "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" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CheckUserSecuritySettingLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCheckUserSecuritySettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckUserSecuritySettingLogic { + return &CheckUserSecuritySettingLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CheckUserSecuritySettingLogic) CheckUserSecuritySetting() (resp *types.UserSecuritySettingResponse, err error) { + uid, ok := l.ctx.Value("user_id").(string) + if !ok { + return nil, errors.New("user_id not found") + } + authUser := l.svcCtx.DB.ScaAuthUser + userSocial := l.svcCtx.DB.ScaAuthUserSocial + var user model.ScaAuthUser + err = authUser.Where(authUser.UID.Eq(uid)).Scan(&user) + if err != nil { + return nil, err + } + // 查询用户社交信息 + var socials []model.ScaAuthUserSocial + err = userSocial.Where(userSocial.UserID.Eq(uid)).Scan(&socials) + if err != nil { + return nil, err + } + resp = &types.UserSecuritySettingResponse{ + SetPassword: user.Password != "", + BindEmail: user.Email != "", + BindPhone: user.Phone != "", + } + // 遍历社交信息以设置绑定状态 + for _, social := range socials { + switch social.Source { + case "wechat": + resp.BindWechat = true + case "qq": + resp.BindQQ = true + case "gitee": + resp.BindGitee = true + case "github": + resp.BindGitHub = true + } + } + return resp, nil +} diff --git a/app/auth/api/internal/logic/share/upload_share_image_logic.go b/app/auth/api/internal/logic/share/upload_share_image_logic.go index d95135c..b0d9213 100644 --- a/app/auth/api/internal/logic/share/upload_share_image_logic.go +++ b/app/auth/api/internal/logic/share/upload_share_image_logic.go @@ -163,7 +163,7 @@ func (l *UploadShareImageLogic) uploadImageAndRecord(tx *query.QueryTx, uid stri UserID: uid, Path: originObjectKey, FileName: img.FileName, - FileSize: strconv.Itoa(size), + FileSize: int64(size), FileType: img.FileType, Width: float64(width), Height: float64(height), diff --git a/app/auth/api/internal/logic/storage/get_share_recent_info_logic.go b/app/auth/api/internal/logic/storage/get_share_recent_info_logic.go new file mode 100644 index 0000000..3e8f156 --- /dev/null +++ b/app/auth/api/internal/logic/storage/get_share_recent_info_logic.go @@ -0,0 +1,112 @@ +package storage + +import ( + "context" + "errors" + "time" + + "schisandra-album-cloud-microservices/app/auth/api/internal/svc" + "schisandra-album-cloud-microservices/app/auth/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetShareRecentInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetShareRecentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetShareRecentInfoLogic { + return &GetShareRecentInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetShareRecentInfoLogic) GetShareRecentInfo() (resp *types.ShareRecentInfoResponse, err error) { + uid, ok := l.ctx.Value("user_id").(string) + if !ok { + return nil, errors.New("user_id not found") + } + // 生成最近7天的日期列表(包含今天) + now := time.Now().Truncate(24 * time.Hour) + dates := make([]string, 7) + for i := 0; i < 7; i++ { + date := now.AddDate(0, 0, -i).Format("2006-01-02") + dates[6-i] = date // 保证日期顺序从旧到新 + } + + // 计算查询时间范围 + startDate := now.AddDate(0, 0, -6) + endDate := now + // 查询每日发布次数 + var publishStats []struct { + Date string + Count int64 + } + storageShare := l.svcCtx.DB.ScaStorageShare + err = storageShare. + Select(storageShare.CreatedAt.Date().As("date"), + storageShare.ID.Count().As("count")). + Where(storageShare.UserID.Eq(uid)). + Where(storageShare.CreatedAt.Between(startDate, endDate)). + Group(storageShare.CreatedAt.Date()). + Scan(&publishStats) + if err != nil { + return nil, err + } + // 查询每日访问数据 + var visitStats []struct { + Date string + VisitCount int64 + VisitorCount int64 + } + shareVisit := l.svcCtx.DB.ScaStorageShareVisit + err = shareVisit.Select( + shareVisit.CreatedAt.Date().As("date"), + shareVisit.ID.Count().As("visit_count"), + shareVisit.UserID.Distinct().Count().As("visitor_count")). + Join(storageShare, shareVisit.ShareID.EqCol(storageShare.ID)). + Where(storageShare.UserID.Eq(uid), + shareVisit.CreatedAt.Between(startDate, endDate)). + Group(shareVisit.CreatedAt.Date()). + Scan(&visitStats) + if err != nil { + return nil, err + } + // 初始化结果映射 + resultMap := make(map[string]*types.ShareRecentMeta) + for _, date := range dates { + resultMap[date] = &types.ShareRecentMeta{ + Date: date, + VisitCount: 0, + VisitorCount: 0, + PublishCount: 0, + } + } + + // 填充发布数据 + for _, stat := range publishStats { + if meta, exists := resultMap[stat.Date]; exists { + meta.PublishCount = stat.Count + } + } + + // 填充访问数据 + for _, stat := range visitStats { + if meta, exists := resultMap[stat.Date]; exists { + meta.VisitCount = stat.VisitCount + meta.VisitorCount = stat.VisitorCount + } + } + + // 构建有序结果 + records := make([]types.ShareRecentMeta, 0, 7) + for _, date := range dates { + records = append(records, *resultMap[date]) + } + + return &types.ShareRecentInfoResponse{Records: records}, nil +} diff --git a/app/auth/api/internal/logic/storage/get_user_upload_info_logic.go b/app/auth/api/internal/logic/storage/get_user_upload_info_logic.go new file mode 100644 index 0000000..bffaf17 --- /dev/null +++ b/app/auth/api/internal/logic/storage/get_user_upload_info_logic.go @@ -0,0 +1,96 @@ +package storage + +import ( + "context" + "errors" + "time" + + "schisandra-album-cloud-microservices/app/auth/api/internal/svc" + "schisandra-album-cloud-microservices/app/auth/api/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetUserUploadInfoLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetUserUploadInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserUploadInfoLogic { + return &GetUserUploadInfoLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetUserUploadInfoLogic) GetUserUploadInfo() (resp *types.UserUploadInfoResponse, err error) { + uid, ok := l.ctx.Value("user_id").(string) + if !ok { + return nil, errors.New("user_id not found") + } + // 图片数 + storageInfo := l.svcCtx.DB.ScaStorageInfo + var imageResult struct { + ImageCount int64 `json:"image_count"` + FileSizeCount int64 `json:"file_size_count"` + } + err = storageInfo.Select(storageInfo.ID.Count().As("image_count"), + storageInfo.FileSize.Sum().As("file_size_count")).Where(storageInfo.UserID.Eq(uid)).Scan(&imageResult) + if err != nil { + return nil, err + } + // 分享数 + storageShare := l.svcCtx.DB.ScaStorageShare + var shareCount int64 + err = storageShare.Select(storageShare.ID.Count().As("share_count")).Where(storageShare.UserID.Eq(uid)).Scan(&shareCount) + if err != nil { + return nil, err + } + // 今日上传数 + var todayResult struct { + TodayUploadCount int64 `json:"today_upload_count"` + TodayFileSizeCount int64 `json:"today_file_size_count"` + } + err = storageInfo.Select( + storageInfo.ID.Count().As("today_upload_count"), + storageInfo.FileSize.Sum().As("today_file_size_count")).Where(storageInfo.UserID.Eq(uid), + storageInfo.CreatedAt.Gte(time.Now().Truncate(24*time.Hour))).Scan(&todayResult) + if err != nil { + return nil, err + } + + // 今日分享数 + var todayShareCount int64 + err = storageShare.Select(storageShare.ID.Count().As("today_share_count")).Where(storageShare.UserID.Eq(uid), + storageShare.CreatedAt.Gte(time.Now().Truncate(24*time.Hour))).Scan(&todayShareCount) + if err != nil { + return nil, err + } + + // 热力图 + heatmap := make([]types.HeatmapMeta, 0) + err = storageInfo.Select( + storageInfo.CreatedAt.Date().As("date"), + storageInfo.ID.Count().As("count"), + ). + Where(storageInfo.UserID.Eq(uid)). + Group(storageInfo.CreatedAt.Date()). + Order(storageInfo.CreatedAt.Date().Desc()). + Scan(&heatmap) + if err != nil { + return nil, err + } + resp = &types.UserUploadInfoResponse{ + ImageCount: imageResult.ImageCount, + TodayUploadCount: todayResult.TodayUploadCount, + ShareCount: shareCount, + TodayShareCount: todayShareCount, + FileSizeCount: imageResult.FileSizeCount, + TodayFileSizeCount: todayResult.TodayFileSizeCount, + Heatmap: heatmap, + } + + return resp, nil +} diff --git a/app/auth/api/internal/mq/image_process_consumer.go b/app/auth/api/internal/mq/image_process_consumer.go index 8196851..02e1c0b 100644 --- a/app/auth/api/internal/mq/image_process_consumer.go +++ b/app/auth/api/internal/mq/image_process_consumer.go @@ -280,7 +280,7 @@ func (c *NsqImageProcessConsumer) saveFileInfoToDB(uid, bucket, provider string, Provider: provider, Bucket: bucket, FileName: fileName, - FileSize: strconv.FormatInt(fileSize, 10), + FileSize: fileSize, FileType: result.FileType, Path: filePath, FaceID: faceId, diff --git a/app/auth/api/internal/types/types.go b/app/auth/api/internal/types/types.go index 05d3e44..624fa0b 100644 --- a/app/auth/api/internal/types/types.go +++ b/app/auth/api/internal/types/types.go @@ -50,7 +50,7 @@ type AlbumDetailListResponse struct { } type AlbumListRequest struct { - Type int64 `json:"type,omitempty"` + Type int64 `json:"type,optional"` Sort bool `json:"sort"` } @@ -98,15 +98,15 @@ type BucketCapacityResponse struct { type CommentContent struct { NickName string `json:"nickname"` Avatar string `json:"avatar"` - Level int64 `json:"level,omitempty" default:"0"` + Level int64 `json:"level,optional" default:"0"` Id int64 `json:"id"` UserId string `json:"user_id"` TopicId string `json:"topic_id"` Content string `json:"content"` - ReplyTo int64 `json:"reply_to,omitempty"` - ReplyId int64 `json:"reply_id,omitempty"` - ReplyUser string `json:"reply_user,omitempty"` - ReplyNickname string `json:"reply_nickname,omitempty"` + ReplyTo int64 `json:"reply_to,optional"` + ReplyId int64 `json:"reply_id,optional"` + ReplyUser string `json:"reply_user,optional"` + ReplyNickname string `json:"reply_nickname,optional"` IsAuthor int64 `json:"is_author"` Likes int64 `json:"likes"` ReplyCount int64 `json:"reply_count"` @@ -115,7 +115,7 @@ type CommentContent struct { Browser string `json:"browser"` OperatingSystem string `json:"operating_system"` IsLiked bool `json:"is_liked" default:"false"` - Images string `json:"images,omitempty"` + Images string `json:"images,optional"` } type CommentDisLikeRequest struct { @@ -161,9 +161,9 @@ type CommentResponse struct { Browser string `json:"browser"` OperatingSystem string `json:"operating_system"` CreatedTime string `json:"created_time"` - ReplyId int64 `json:"reply_id,omitempty"` - ReplyUser string `json:"reply_user,omitempty"` - ReplyTo int64 `json:"reply_to,omitempty"` + ReplyId int64 `json:"reply_id,optional"` + ReplyUser string `json:"reply_user,optional"` + ReplyTo int64 `json:"reply_to,optional"` } type DeleteImageRequest struct { @@ -222,6 +222,11 @@ type FaceSampleLibraryListResponse struct { Faces []FaceSampleLibrary `json:"faces"` } +type HeatmapMeta struct { + Date string `json:"date"` + Count int64 `json:"count"` +} + type ImageMeta struct { ID int64 `json:"id"` FileName string `json:"file_name"` @@ -267,7 +272,7 @@ type LoginResponse struct { AccessToken string `json:"access_token"` ExpireAt int64 `json:"expire_at"` UID string `json:"uid"` - Username string `json:"username,omitempty"` + Username string `json:"username,optional"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` Status int64 `json:"status"` @@ -312,7 +317,7 @@ type QueryDeleteRecordRequest struct { type QueryShareImageRequest struct { InviteCode string `json:"invite_code"` - AccessPassword string `json:"access_password,omitempty"` + AccessPassword string `json:"access_password,optional"` } type QueryShareImageResponse struct { @@ -321,7 +326,7 @@ type QueryShareImageResponse struct { type QueryShareInfoRequest struct { InviteCode string `json:"invite_code"` - AccessPassword string `json:"access_password,omitempty"` + AccessPassword string `json:"access_password,optional"` } type RecentListRequest struct { @@ -403,8 +408,8 @@ type SearchImageResponse struct { type ShareAlbumRequest struct { ID int64 `json:"id"` ExpireDate string `json:"expire_date"` - AccessLimit int64 `json:"access_limit,omitempty"` - AccessPassword string `json:"access_password,omitempty"` + AccessLimit int64 `json:"access_limit,optional"` + AccessPassword string `json:"access_password,optional"` Provider string `json:"provider"` Bucket string `json:"bucket"` } @@ -420,10 +425,10 @@ type ShareImageMeta struct { } type ShareImageRequest struct { - Title string `json:"title,omitempty"` + Title string `json:"title,optional"` ExpireDate string `json:"expire_date"` - AccessLimit int64 `json:"access_limit,omitempty"` - AccessPassword string `json:"access_password,omitempty"` + AccessLimit int64 `json:"access_limit,optional"` + AccessPassword string `json:"access_password,optional"` Images []ShareImageMeta `json:"images"` } @@ -461,6 +466,17 @@ type SharePhoneUploadRequest struct { UserId string `json:"user_id"` } +type ShareRecentInfoResponse struct { + Records []ShareRecentMeta `json:"records"` +} + +type ShareRecentMeta struct { + Date string `json:"date"` + VisitCount int64 `json:"visit_count"` + VisitorCount int64 `json:"visitor_count"` + PublishCount int64 `json:"publish_count"` +} + type ShareRecord struct { ID int64 `json:"id"` CoverImage string `json:"cover_image"` @@ -576,6 +592,26 @@ type UploadRequest struct { UserId string `json:"user_id"` } +type UserSecuritySettingResponse struct { + BindPhone bool `json:"bind_phone,default=false"` + BindEmail bool `json:"bind_email,default=falsel"` + BindWechat bool `json:"bind_wechat,default=false"` + BindQQ bool `json:"bind_qq,default=false"` + BindGitHub bool `json:"bind_github,default=false"` + BindGitee bool `json:"bind_gitee,default=false"` + SetPassword bool `json:"set_password,default=false"` +} + +type UserUploadInfoResponse struct { + ImageCount int64 `json:"image_count"` + TodayUploadCount int64 `json:"today_upload_count"` + ShareCount int64 `json:"share_count"` + TodayShareCount int64 `json:"today_share_count"` + FileSizeCount int64 `json:"file_size_count"` + TodayFileSizeCount int64 `json:"today_file_size_count"` + Heatmap []HeatmapMeta `json:"heatmap"` +} + type WechatOffiaccountLoginRequest struct { Openid string `json:"openid"` ClientId string `json:"client_id"` diff --git a/app/auth/model/mysql/model/sca_storage_info.gen.go b/app/auth/model/mysql/model/sca_storage_info.gen.go index 00b6a6f..18f232d 100644 --- a/app/auth/model/mysql/model/sca_storage_info.gen.go +++ b/app/auth/model/mysql/model/sca_storage_info.gen.go @@ -20,7 +20,7 @@ type ScaStorageInfo struct { Bucket string `gorm:"column:bucket;type:varchar(50);comment:存储桶" json:"bucket"` // 存储桶 Path string `gorm:"column:path;type:text;comment:路径" json:"path"` // 路径 FileName string `gorm:"column:file_name;type:varchar(100);comment:文件名称" json:"file_name"` // 文件名称 - FileSize string `gorm:"column:file_size;type:varchar(50);comment:文件大小" json:"file_size"` // 文件大小 + FileSize int64 `gorm:"column:file_size;type:bigint(20);comment:文件大小" json:"file_size"` // 文件大小 FileType string `gorm:"column:file_type;type:varchar(50);comment:文件类型" json:"file_type"` // 文件类型 Width float64 `gorm:"column:width;type:double;comment:宽" json:"width"` // 宽 Height float64 `gorm:"column:height;type:double;comment:高" json:"height"` // 高 diff --git a/app/auth/model/mysql/query/sca_storage_info.gen.go b/app/auth/model/mysql/query/sca_storage_info.gen.go index 40812a0..303d57e 100644 --- a/app/auth/model/mysql/query/sca_storage_info.gen.go +++ b/app/auth/model/mysql/query/sca_storage_info.gen.go @@ -33,7 +33,7 @@ func newScaStorageInfo(db *gorm.DB, opts ...gen.DOOption) scaStorageInfo { _scaStorageInfo.Bucket = field.NewString(tableName, "bucket") _scaStorageInfo.Path = field.NewString(tableName, "path") _scaStorageInfo.FileName = field.NewString(tableName, "file_name") - _scaStorageInfo.FileSize = field.NewString(tableName, "file_size") + _scaStorageInfo.FileSize = field.NewInt64(tableName, "file_size") _scaStorageInfo.FileType = field.NewString(tableName, "file_type") _scaStorageInfo.Width = field.NewFloat64(tableName, "width") _scaStorageInfo.Height = field.NewFloat64(tableName, "height") @@ -62,7 +62,7 @@ type scaStorageInfo struct { Bucket field.String // 存储桶 Path field.String // 路径 FileName field.String // 文件名称 - FileSize field.String // 文件大小 + FileSize field.Int64 // 文件大小 FileType field.String // 文件类型 Width field.Float64 // 宽 Height field.Float64 // 高 @@ -96,7 +96,7 @@ func (s *scaStorageInfo) updateTableName(table string) *scaStorageInfo { s.Bucket = field.NewString(table, "bucket") s.Path = field.NewString(table, "path") s.FileName = field.NewString(table, "file_name") - s.FileSize = field.NewString(table, "file_size") + s.FileSize = field.NewInt64(table, "file_size") s.FileType = field.NewString(table, "file_type") s.Width = field.NewFloat64(table, "width") s.Height = field.NewFloat64(table, "height") diff --git a/common/img_encrypt/img_encrypt_test.go b/common/img_encrypt/img_encrypt_test.go new file mode 100644 index 0000000..171c937 --- /dev/null +++ b/common/img_encrypt/img_encrypt_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "image" + "image/draw" + "image/png" + "log" + "os" + "testing" +) + +func TestImgEncrypt(t *testing.T) { + // 1. 读取并强制转换为RGBA + inputFile, err := os.Open("E:\\Go_WorkSpace\\schisandra-album-cloud-microservices\\common\\img_encrypt\\input.png") + if err != nil { + log.Fatal("打开文件失败:", err) + } + defer inputFile.Close() + + srcImg, err := png.Decode(inputFile) + if err != nil { + log.Fatal("解码失败:", err) + } + + bounds := srcImg.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, srcImg, bounds.Min, draw.Src) + + // 2. 安全加密(处理有效像素区) + key := []byte{0x1F, 0x3A, 0x7B, 0x9C} // 示例密钥(推荐长度4/8/16) + secureXor(rgba, key) + + // 3. 保存加密图像(禁用压缩) + outputFile, err := os.Create("encrypted.png") + if err != nil { + log.Fatal("创建文件失败:", err) + } + defer outputFile.Close() + + encoder := png.Encoder{CompressionLevel: png.NoCompression} + if err := encoder.Encode(outputFile, rgba); err != nil { + log.Fatal("保存失败:", err) + } +} + +func TestImgDecrypt(t *testing.T) { + // 1. 读取加密图像 + inputFile, err := os.Open("E:\\Go_WorkSpace\\schisandra-album-cloud-microservices\\common\\img_encrypt\\encrypted.png") + if err != nil { + log.Fatal("打开加密文件失败:", err) + } + defer inputFile.Close() + + encImg, err := png.Decode(inputFile) + if err != nil { + log.Fatal("解码失败:", err) + } + + // 2. 转换为RGBA + bounds := encImg.Bounds() + rgba := image.NewRGBA(bounds) + draw.Draw(rgba, bounds, encImg, bounds.Min, draw.Src) + + // 3. 解密(使用相同密钥) + key := []byte{0x1F, 0x3A, 0x7B, 0x9C} // 必须与加密一致 + secureXor(rgba, key) + + // 4. 保存解密结果 + outputFile, err := os.Create("decrypted.png") + if err != nil { + log.Fatal("创建解密文件失败:", err) + } + defer outputFile.Close() + + encoder := png.Encoder{CompressionLevel: png.NoCompression} + if err := encoder.Encode(outputFile, rgba); err != nil { + log.Fatal("保存失败:", err) + } +} + +// 通用加密/解密函数 +// 安全加密函数 +func secureXor(img *image.RGBA, key []byte) { + keyLen := len(key) + if keyLen == 0 { + log.Fatal("密钥不能为空") + } + + bounds := img.Bounds() + data := img.Pix + stride := img.Stride + width := bounds.Dx() * 4 // 每行实际需要的字节数 + + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + rowStart := (y - bounds.Min.Y) * stride + // 严格限定处理范围为有效像素区 + end := rowStart + width + if end > len(data) { + end = len(data) + } + + for pos := rowStart; pos < end; pos++ { + data[pos] ^= key[pos%keyLen] + } + } +} diff --git a/common/img_encrypt/input.png b/common/img_encrypt/input.png new file mode 100644 index 0000000..2e07d01 Binary files /dev/null and b/common/img_encrypt/input.png differ