From 820d54108dc57633deac7df0fd0dd9ce939e8eff Mon Sep 17 00:00:00 2001 From: Michael Li Date: Thu, 29 Dec 2022 18:36:03 +0800 Subject: [PATCH] mir:add priv api implement for new web service --- auto/api/v1/priv.go | 234 +++++++++--- internal/model/web/core.go | 3 - internal/model/web/priv.go | 128 ++++++- internal/servants/base/base.go | 74 ++++ internal/servants/localoss/xerror.go | 15 - internal/servants/web/core.go | 63 +--- internal/servants/web/priv.go | 545 ++++++++++++++++++++++++++- internal/servants/web/utils.go | 98 +++++ internal/servants/web/web.go | 6 +- internal/servants/web/xerror.go | 4 + mirc/web/v1/priv.go | 18 +- 11 files changed, 1033 insertions(+), 155 deletions(-) delete mode 100644 internal/servants/localoss/xerror.go diff --git a/auto/api/v1/priv.go b/auto/api/v1/priv.go index e5c19fd2..29b39a92 100644 --- a/auto/api/v1/priv.go +++ b/auto/api/v1/priv.go @@ -18,22 +18,31 @@ type Priv interface { CreateCommentReply() mir.Error DeleteComment() mir.Error CreateComment() mir.Error - VisiblePost() mir.Error - StickTweet() mir.Error - LockTweet() mir.Error - CollectionTweet() mir.Error - StarTweet() mir.Error - DeleteTweet() mir.Error + VisiblePost(*web.VisiblePostReq) (*web.VisiblePostResp, mir.Error) + StickTweet(*web.StickTweetReq) (*web.StickTweetResp, mir.Error) + LockTweet(*web.LockTweetReq) (*web.LockTweetResp, mir.Error) + CollectionTweet(*web.CollectionTweetReq) (*web.CollectionTweetResp, mir.Error) + StarTweet(*web.StarTweetReq) (*web.StarTweetResp, mir.Error) + DeleteTweet(*web.DeleteTweetReq) mir.Error CreateTweet(*web.CreateTweetReq) (*web.CreateTweetResp, mir.Error) - DownloadAttachment() mir.Error - DownloadAttachmentPrecheck() mir.Error - UploadAttachment() mir.Error + DownloadAttachment(*web.DownloadAttachmentReq) (*web.DownloadAttachmentResp, mir.Error) + DownloadAttachmentPrecheck(*web.DownloadAttachmentPrecheckReq) (*web.DownloadAttachmentPrecheckResp, mir.Error) + UploadAttachment(*web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) mustEmbedUnimplementedPrivServant() } type PrivBinding interface { + BindVisiblePost(*gin.Context) (*web.VisiblePostReq, mir.Error) + BindStickTweet(*gin.Context) (*web.StickTweetReq, mir.Error) + BindLockTweet(*gin.Context) (*web.LockTweetReq, mir.Error) + BindCollectionTweet(*gin.Context) (*web.CollectionTweetReq, mir.Error) + BindStarTweet(*gin.Context) (*web.StarTweetReq, mir.Error) + BindDeleteTweet(*gin.Context) (*web.DeleteTweetReq, mir.Error) BindCreateTweet(*gin.Context) (*web.CreateTweetReq, mir.Error) + BindDownloadAttachment(*gin.Context) (*web.DownloadAttachmentReq, mir.Error) + BindDownloadAttachmentPrecheck(*gin.Context) (*web.DownloadAttachmentPrecheckReq, mir.Error) + BindUploadAttachment(*gin.Context) (*web.UploadAttachmentReq, mir.Error) mustEmbedUnimplementedPrivBinding() } @@ -43,16 +52,16 @@ type PrivRender interface { RenderCreateCommentReply(*gin.Context, mir.Error) RenderDeleteComment(*gin.Context, mir.Error) RenderCreateComment(*gin.Context, mir.Error) - RenderVisiblePost(*gin.Context, mir.Error) - RenderStickTweet(*gin.Context, mir.Error) - RenderLockTweet(*gin.Context, mir.Error) - RenderCollectionTweet(*gin.Context, mir.Error) - RenderStarTweet(*gin.Context, mir.Error) + RenderVisiblePost(*gin.Context, *web.VisiblePostResp, mir.Error) + RenderStickTweet(*gin.Context, *web.StickTweetResp, mir.Error) + RenderLockTweet(*gin.Context, *web.LockTweetResp, mir.Error) + RenderCollectionTweet(*gin.Context, *web.CollectionTweetResp, mir.Error) + RenderStarTweet(*gin.Context, *web.StarTweetResp, mir.Error) RenderDeleteTweet(*gin.Context, mir.Error) RenderCreateTweet(*gin.Context, *web.CreateTweetResp, mir.Error) - RenderDownloadAttachment(*gin.Context, mir.Error) - RenderDownloadAttachmentPrecheck(*gin.Context, mir.Error) - RenderUploadAttachment(*gin.Context, mir.Error) + RenderDownloadAttachment(*gin.Context, *web.DownloadAttachmentResp, mir.Error) + RenderDownloadAttachmentPrecheck(*gin.Context, *web.DownloadAttachmentPrecheckResp, mir.Error) + RenderUploadAttachment(*gin.Context, *web.UploadAttachmentResp, mir.Error) mustEmbedUnimplementedPrivRender() } @@ -112,7 +121,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderVisiblePost(c, s.VisiblePost()) + req, err := b.BindVisiblePost(c) + if err != nil { + r.RenderVisiblePost(c, nil, err) + return + } + resp, err := s.VisiblePost(req) + r.RenderVisiblePost(c, resp, err) }) router.Handle("POST", "/post/stick", func(c *gin.Context) { @@ -122,7 +137,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderStickTweet(c, s.StickTweet()) + req, err := b.BindStickTweet(c) + if err != nil { + r.RenderStickTweet(c, nil, err) + return + } + resp, err := s.StickTweet(req) + r.RenderStickTweet(c, resp, err) }) router.Handle("POST", "/post/lock", func(c *gin.Context) { @@ -132,7 +153,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderLockTweet(c, s.LockTweet()) + req, err := b.BindLockTweet(c) + if err != nil { + r.RenderLockTweet(c, nil, err) + return + } + resp, err := s.LockTweet(req) + r.RenderLockTweet(c, resp, err) }) router.Handle("POST", "/post/collection", func(c *gin.Context) { @@ -142,7 +169,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderCollectionTweet(c, s.CollectionTweet()) + req, err := b.BindCollectionTweet(c) + if err != nil { + r.RenderCollectionTweet(c, nil, err) + return + } + resp, err := s.CollectionTweet(req) + r.RenderCollectionTweet(c, resp, err) }) router.Handle("POST", "/post/start", func(c *gin.Context) { @@ -152,7 +185,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderStarTweet(c, s.StarTweet()) + req, err := b.BindStarTweet(c) + if err != nil { + r.RenderStarTweet(c, nil, err) + return + } + resp, err := s.StarTweet(req) + r.RenderStarTweet(c, resp, err) }) router.Handle("DELETE", "/post", func(c *gin.Context) { @@ -162,7 +201,12 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderDeleteTweet(c, s.DeleteTweet()) + req, err := b.BindDeleteTweet(c) + if err != nil { + r.RenderDeleteTweet(c, err) + return + } + r.RenderDeleteTweet(c, s.DeleteTweet(req)) }) router.Handle("POST", "/post", func(c *gin.Context) { @@ -188,7 +232,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderDownloadAttachment(c, s.DownloadAttachment()) + req, err := b.BindDownloadAttachment(c) + if err != nil { + r.RenderDownloadAttachment(c, nil, err) + return + } + resp, err := s.DownloadAttachment(req) + r.RenderDownloadAttachment(c, resp, err) }) router.Handle("GET", "/attachment/precheck", func(c *gin.Context) { @@ -198,7 +248,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderDownloadAttachmentPrecheck(c, s.DownloadAttachmentPrecheck()) + req, err := b.BindDownloadAttachmentPrecheck(c) + if err != nil { + r.RenderDownloadAttachmentPrecheck(c, nil, err) + return + } + resp, err := s.DownloadAttachmentPrecheck(req) + r.RenderDownloadAttachmentPrecheck(c, resp, err) }) router.Handle("POST", "/attachment", func(c *gin.Context) { @@ -208,7 +264,13 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) { default: } - r.RenderUploadAttachment(c, s.UploadAttachment()) + req, err := b.BindUploadAttachment(c) + if err != nil { + r.RenderUploadAttachment(c, nil, err) + return + } + resp, err := s.UploadAttachment(req) + r.RenderUploadAttachment(c, resp, err) }) } @@ -237,27 +299,27 @@ func (UnimplementedPrivServant) CreateComment() mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) VisiblePost() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) VisiblePost(req *web.VisiblePostReq) (*web.VisiblePostResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) StickTweet() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) StickTweet(req *web.StickTweetReq) (*web.StickTweetResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) LockTweet() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) LockTweet(req *web.LockTweetReq) (*web.LockTweetResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) CollectionTweet() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) CollectionTweet(req *web.CollectionTweetReq) (*web.CollectionTweetResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) StarTweet() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) StarTweet(req *web.StarTweetReq) (*web.StarTweetResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) DeleteTweet() mir.Error { +func (UnimplementedPrivServant) DeleteTweet(req *web.DeleteTweetReq) mir.Error { return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } @@ -265,16 +327,16 @@ func (UnimplementedPrivServant) CreateTweet(req *web.CreateTweetReq) (*web.Creat return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) DownloadAttachment() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) DownloadAttachment(req *web.DownloadAttachmentReq) (*web.DownloadAttachmentResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) DownloadAttachmentPrecheck() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) DownloadAttachmentPrecheck(req *web.DownloadAttachmentPrecheckReq) (*web.DownloadAttachmentPrecheckResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } -func (UnimplementedPrivServant) UploadAttachment() mir.Error { - return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) +func (UnimplementedPrivServant) UploadAttachment(req *web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) { + return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) } func (UnimplementedPrivServant) mustEmbedUnimplementedPrivServant() {} @@ -300,24 +362,24 @@ func (r *UnimplementedPrivRender) RenderCreateComment(c *gin.Context, err mir.Er r.RenderAny(c, nil, err) } -func (r *UnimplementedPrivRender) RenderVisiblePost(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderVisiblePost(c *gin.Context, data *web.VisiblePostResp, err mir.Error) { + r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderStickTweet(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderStickTweet(c *gin.Context, data *web.StickTweetResp, err mir.Error) { + r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderLockTweet(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderLockTweet(c *gin.Context, data *web.LockTweetResp, err mir.Error) { + r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderCollectionTweet(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderCollectionTweet(c *gin.Context, data *web.CollectionTweetResp, err mir.Error) { + r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderStarTweet(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderStarTweet(c *gin.Context, data *web.StarTweetResp, err mir.Error) { + r.RenderAny(c, data, err) } func (r *UnimplementedPrivRender) RenderDeleteTweet(c *gin.Context, err mir.Error) { @@ -328,16 +390,16 @@ func (r *UnimplementedPrivRender) RenderCreateTweet(c *gin.Context, data *web.Cr r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderDownloadAttachment(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderDownloadAttachment(c *gin.Context, data *web.DownloadAttachmentResp, err mir.Error) { + r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderDownloadAttachmentPrecheck(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderDownloadAttachmentPrecheck(c *gin.Context, data *web.DownloadAttachmentPrecheckResp, err mir.Error) { + r.RenderAny(c, data, err) } -func (r *UnimplementedPrivRender) RenderUploadAttachment(c *gin.Context, err mir.Error) { - r.RenderAny(c, nil, err) +func (r *UnimplementedPrivRender) RenderUploadAttachment(c *gin.Context, data *web.UploadAttachmentResp, err mir.Error) { + r.RenderAny(c, data, err) } func (r *UnimplementedPrivRender) mustEmbedUnimplementedPrivRender() {} @@ -347,10 +409,64 @@ type UnimplementedPrivBinding struct { BindAny func(*gin.Context, any) mir.Error } +func (b *UnimplementedPrivBinding) BindVisiblePost(c *gin.Context) (*web.VisiblePostReq, mir.Error) { + obj := new(web.VisiblePostReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindStickTweet(c *gin.Context) (*web.StickTweetReq, mir.Error) { + obj := new(web.StickTweetReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindLockTweet(c *gin.Context) (*web.LockTweetReq, mir.Error) { + obj := new(web.LockTweetReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindCollectionTweet(c *gin.Context) (*web.CollectionTweetReq, mir.Error) { + obj := new(web.CollectionTweetReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindStarTweet(c *gin.Context) (*web.StarTweetReq, mir.Error) { + obj := new(web.StarTweetReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindDeleteTweet(c *gin.Context) (*web.DeleteTweetReq, mir.Error) { + obj := new(web.DeleteTweetReq) + err := b.BindAny(c, obj) + return obj, err +} + func (b *UnimplementedPrivBinding) BindCreateTweet(c *gin.Context) (*web.CreateTweetReq, mir.Error) { obj := new(web.CreateTweetReq) err := b.BindAny(c, obj) return obj, err } +func (b *UnimplementedPrivBinding) BindDownloadAttachment(c *gin.Context) (*web.DownloadAttachmentReq, mir.Error) { + obj := new(web.DownloadAttachmentReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindDownloadAttachmentPrecheck(c *gin.Context) (*web.DownloadAttachmentPrecheckReq, mir.Error) { + obj := new(web.DownloadAttachmentPrecheckReq) + err := b.BindAny(c, obj) + return obj, err +} + +func (b *UnimplementedPrivBinding) BindUploadAttachment(c *gin.Context) (*web.UploadAttachmentReq, mir.Error) { + obj := new(web.UploadAttachmentReq) + err := b.BindAny(c, obj) + return obj, err +} + func (b *UnimplementedPrivBinding) mustEmbedUnimplementedPrivBinding() {} diff --git a/internal/model/web/core.go b/internal/model/web/core.go index c0e44e85..c43a0905 100644 --- a/internal/model/web/core.go +++ b/internal/model/web/core.go @@ -5,8 +5,6 @@ package web import ( - "context" - "github.com/rocboss/paopao-ce/internal/servants/base" ) @@ -17,7 +15,6 @@ type ChangeAvatarReq struct { type SyncSearchIndexReq struct { BaseInfo `json:"-" binding:"-"` - Ctx context.Context `json:"-" binding:"-"` } type UserInfoReq struct { diff --git a/internal/model/web/priv.go b/internal/model/web/priv.go index 9184a437..290f80bd 100644 --- a/internal/model/web/priv.go +++ b/internal/model/web/priv.go @@ -4,10 +4,132 @@ package web +import ( + "fmt" + "mime/multipart" + "strings" + + "github.com/rocboss/paopao-ce/internal/core" +) + +type PostContentItem struct { + Content string `json:"content" binding:"required"` + Type core.PostContentT `json:"type" binding:"required"` + Sort int64 `json:"sort" binding:"required"` +} + type CreateTweetReq struct { - *BaseInfo `json:"-"` + BaseInfo `json:"-" binding:"-"` + Contents []*PostContentItem `json:"contents" binding:"required"` + Tags []string `json:"tags" binding:"required"` + Users []string `json:"users" binding:"required"` + AttachmentPrice int64 `json:"attachment_price"` + Visibility core.PostVisibleT `json:"visibility"` + ClientIP string `json:"-" binding:"-"` +} + +type CreateTweetResp core.PostFormated + +type DeleteTweetReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} + +type StarTweetReq struct { + SimpleInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} + +type StarTweetResp struct { + Status bool `json:"status"` +} + +type CollectionTweetReq struct { + SimpleInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} + +type CollectionTweetResp struct { + Status bool `json:"status"` +} + +type LockTweetReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} + +type LockTweetResp struct { + LockStatus int `json:"lock_status"` +} + +type StickTweetReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` +} + +type StickTweetResp struct { + StickStatus int `json:"top_status"` +} + +type VisiblePostReq struct { + BaseInfo `json:"-" binding:"-"` + ID int64 `json:"id" binding:"required"` + Visibility core.PostVisibleT `json:"visibility" binding:"required"` +} + +type VisiblePostResp struct { + Visibility core.PostVisibleT `json:"visibility"` +} + +type UploadAttachmentReq struct { + SimpleInfo `json:"-" binding:"-"` + UploadType string + ContentType string + File multipart.File + FileSize int64 + FileExt string +} + +type UploadAttachmentResp struct { + UserID int64 `json:"user_id"` + FileSize int64 `json:"file_size"` + ImgWidth int `json:"img_width"` + ImgHeight int `json:"img_height"` + Type core.AttachmentType `json:"type"` + Content string `json:"content"` +} + +type DownloadAttachmentPrecheckReq struct { + BaseInfo `json:"-" binding:"-"` + ContentID int64 `form:"id"` +} + +type DownloadAttachmentPrecheckResp struct { + Paid bool `json:"paid"` +} + +type DownloadAttachmentReq struct { + BaseInfo `json:"-" binding:"-"` + ContentID int64 `form:"id"` +} + +type DownloadAttachmentResp struct { + SignedURL string `json:"signed_url"` } -type CreateTweetResp struct { - // TODO +// Check 检查PostContentItem属性 +func (p *PostContentItem) Check(acs core.AttachmentCheckService) error { + // 检查附件是否是本站资源 + if p.Type == core.ContentTypeImage || p.Type == core.ContentTypeVideo || p.Type == core.ContentTypeAttachment { + if err := acs.CheckAttachment(p.Content); err != nil { + return err + } + } + // 检查链接是否合法 + if p.Type == core.ContentTypeLink { + if strings.Index(p.Content, "http://") != 0 && strings.Index(p.Content, "https://") != 0 { + return fmt.Errorf("链接不合法") + } + } + return nil } diff --git a/internal/servants/base/base.go b/internal/servants/base/base.go index dc1bbb9e..ad3ab6a7 100644 --- a/internal/servants/base/base.go +++ b/internal/servants/base/base.go @@ -5,7 +5,11 @@ package base import ( + "context" + "fmt" + "math" "net/http" + "time" "github.com/alimy/mir/v3" "github.com/gin-gonic/gin" @@ -22,6 +26,7 @@ type BaseServant struct { type DaoServant struct { Redis *redis.Client Ds core.DataService + Ts core.TweetSearchService } type BaseBinding types.Empty @@ -125,3 +130,72 @@ func (s *DaoServant) GetTweetBy(id int64) (*core.PostFormated, error) { } return postFormated, nil } + +func (s *DaoServant) PushPostsToSearch(c context.Context) { + if ok, _ := s.Redis.SetNX(c, "JOB_PUSH_TO_SEARCH", 1, time.Hour).Result(); ok { + defer s.Redis.Del(c, "JOB_PUSH_TO_SEARCH") + + splitNum := 1000 + totalRows, _ := s.Ds.GetPostCount(&core.ConditionsT{ + "visibility IN ?": []core.PostVisibleT{core.PostVisitPublic, core.PostVisitFriend}, + }) + pages := math.Ceil(float64(totalRows) / float64(splitNum)) + nums := int(pages) + for i := 0; i < nums; i++ { + posts, postsFormated, err := s.getTweetList(&core.ConditionsT{}, i*splitNum, splitNum) + if err != nil || len(posts) != len(postsFormated) { + continue + } + for i, pf := range postsFormated { + contentFormated := "" + for _, content := range pf.Contents { + if content.Type == core.ContentTypeText || content.Type == core.ContentTypeTitle { + contentFormated = contentFormated + content.Content + "\n" + } + } + docs := []core.TsDocItem{{ + Post: posts[i], + Content: contentFormated, + }} + s.Ts.AddDocuments(docs, fmt.Sprintf("%d", posts[i].ID)) + } + } + } +} + +func (s *DaoServant) PushPostToSearch(post *core.Post) { + postFormated := post.Format() + postFormated.User = &core.UserFormated{ + ID: post.UserID, + } + contents, _ := s.Ds.GetPostContentsByIDs([]int64{post.ID}) + for _, content := range contents { + postFormated.Contents = append(postFormated.Contents, content.Format()) + } + + contentFormated := "" + for _, content := range postFormated.Contents { + if content.Type == core.ContentTypeText || content.Type == core.ContentTypeTitle { + contentFormated = contentFormated + content.Content + "\n" + } + } + + docs := []core.TsDocItem{{ + Post: post, + Content: contentFormated, + }} + s.Ts.AddDocuments(docs, fmt.Sprintf("%d", post.ID)) +} + +func (s *DaoServant) DeleteSearchPost(post *core.Post) error { + return s.Ts.DeleteDocuments([]string{fmt.Sprintf("%d", post.ID)}) +} + +func (s *DaoServant) getTweetList(conditions *core.ConditionsT, offset, limit int) ([]*core.Post, []*core.PostFormated, error) { + posts, err := s.Ds.GetPosts(conditions, offset, limit) + if err != nil { + return nil, nil, err + } + postFormated, err := s.Ds.MergePosts(posts) + return posts, postFormated, err +} diff --git a/internal/servants/localoss/xerror.go b/internal/servants/localoss/xerror.go deleted file mode 100644 index 38132d24..00000000 --- a/internal/servants/localoss/xerror.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 ROC. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package localoss - -import ( - "github.com/rocboss/paopao-ce/pkg/xerror" -) - -var ( - errFileUploadFailed = xerror.NewError(10200, "文件上传失败") - errFileInvalidExt = xerror.NewError(10201, "文件类型不合法") - errFileInvalidSize = xerror.NewError(10202, "文件大小超限") -) diff --git a/internal/servants/web/core.go b/internal/servants/web/core.go index 65c49fcb..6173913a 100644 --- a/internal/servants/web/core.go +++ b/internal/servants/web/core.go @@ -7,7 +7,7 @@ package web import ( "context" "fmt" - "math" + "time" "unicode/utf8" @@ -42,7 +42,6 @@ type coreSrv struct { api.UnimplementedCoreServant *base.DaoServant - ts core.TweetSearchService oss core.ObjectStorageService } @@ -55,16 +54,6 @@ type coreRender struct { *api.UnimplementedCoreRender } -func (b *coreBinding) BindSyncSearchIndex(c *gin.Context) (*web.SyncSearchIndexReq, mir.Error) { - user, _ := base.UserFrom(c) - return &web.SyncSearchIndexReq{ - BaseInfo: web.BaseInfo{ - User: user, - }, - Ctx: c.Request.Context(), - }, nil -} - func (b *coreBinding) BindGetUserInfo(c *gin.Context) (*web.UserInfoReq, mir.Error) { username, exist := base.UserNameFrom(c) if !exist { @@ -134,7 +123,7 @@ func (s *coreSrv) Chain() gin.HandlersChain { func (s *coreSrv) SyncSearchIndex(req *web.SyncSearchIndexReq) mir.Error { if req.User != nil && req.User.IsAdmin { - go s.pushPostsToSearch(req.Ctx) + go s.PushPostsToSearch(context.Background()) } return nil } @@ -445,38 +434,6 @@ func (s *coreSrv) ChangeAvatar(req *web.ChangeAvatarReq) (xerr mir.Error) { return nil } -func (s *coreSrv) pushPostsToSearch(c context.Context) { - if ok, _ := s.Redis.SetNX(c, "JOB_PUSH_TO_SEARCH", 1, time.Hour).Result(); ok { - defer s.Redis.Del(c, "JOB_PUSH_TO_SEARCH") - - splitNum := 1000 - totalRows, _ := s.Ds.GetPostCount(&core.ConditionsT{ - "visibility IN ?": []core.PostVisibleT{core.PostVisitPublic, core.PostVisitFriend}, - }) - pages := math.Ceil(float64(totalRows) / float64(splitNum)) - nums := int(pages) - for i := 0; i < nums; i++ { - posts, postsFormated, err := s.getTweetList(&core.ConditionsT{}, i*splitNum, splitNum) - if err != nil || len(posts) != len(postsFormated) { - continue - } - for i, pf := range postsFormated { - contentFormated := "" - for _, content := range pf.Contents { - if content.Type == core.ContentTypeText || content.Type == core.ContentTypeTitle { - contentFormated = contentFormated + content.Content + "\n" - } - } - docs := []core.TsDocItem{{ - Post: posts[i], - Content: contentFormated, - }} - s.ts.AddDocuments(docs, fmt.Sprintf("%d", posts[i].ID)) - } - } - } -} - func (s *coreSrv) TweetCollectionStatus(req *web.TweetCollectionStatusReq) (*web.TweetCollectionStatusResp, mir.Error) { resp := &web.TweetCollectionStatusResp{ Status: true, @@ -499,23 +456,9 @@ func (s *coreSrv) TweetStarStatus(req *web.TweetStarStatusReq) (*web.TweetStarSt return resp, nil } -func (s *coreSrv) deleteSearchPost(post *core.Post) error { - return s.ts.DeleteDocuments([]string{fmt.Sprintf("%d", post.ID)}) -} - -func (s *coreSrv) getTweetList(conditions *core.ConditionsT, offset, limit int) ([]*core.Post, []*core.PostFormated, error) { - posts, err := s.Ds.GetPosts(conditions, offset, limit) - if err != nil { - return nil, nil, err - } - postFormated, err := s.Ds.MergePosts(posts) - return posts, postFormated, err -} - -func newCoreSrv(s *base.DaoServant, ts core.TweetSearchService, oss core.ObjectStorageService) api.Core { +func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Core { return &coreSrv{ DaoServant: s, - ts: ts, oss: oss, } } diff --git a/internal/servants/web/priv.go b/internal/servants/web/priv.go index 5e4890f0..87bc95dd 100644 --- a/internal/servants/web/priv.go +++ b/internal/servants/web/priv.go @@ -5,21 +5,42 @@ package web import ( + "image" + "strings" + + "github.com/alimy/mir/v3" + "github.com/disintegration/imaging" "github.com/gin-gonic/gin" + "github.com/gofrs/uuid" api "github.com/rocboss/paopao-ce/auto/api/v1" + "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/chain" + "github.com/rocboss/paopao-ce/pkg/convert" + "github.com/rocboss/paopao-ce/pkg/util" + "github.com/rocboss/paopao-ce/pkg/xerror" + "github.com/sirupsen/logrus" ) var ( _ api.Priv = (*privSrv)(nil) _ api.PrivBinding = (*privBinding)(nil) _ api.PrivRender = (*privRender)(nil) + + _uploadAttachmentTypeMap = map[string]core.AttachmentType{ + "public/image": core.AttachmentTypeImage, + "public/avatar": core.AttachmentTypeImage, + "public/video": core.AttachmentTypeVideo, + "attachment": core.AttachmentTypeOther, + } ) type privSrv struct { - base.BaseServant api.UnimplementedPrivServant + *base.DaoServant + + oss core.ObjectStorageService } type privBinding struct { @@ -32,12 +53,530 @@ type privRender struct { *api.UnimplementedPrivRender } +func (b *privBinding) BindUploadAttachment(c *gin.Context) (*web.UploadAttachmentReq, mir.Error) { + UserId, exist := base.UserIdFrom(c) + if !exist { + return nil, xerror.UnauthorizedAuthNotExist + } + uploadType := c.Request.FormValue("type") + file, fileHeader, err := c.Request.FormFile("file") + if err != nil { + return nil, _errFileUploadFailed + } + if err := fileCheck(uploadType, fileHeader.Size); err != nil { + return nil, err + } + contentType := fileHeader.Header.Get("Content-Type") + fileExt, xerr := getFileExt(contentType) + if xerr != nil { + return nil, xerr + } + return &web.UploadAttachmentReq{ + SimpleInfo: web.SimpleInfo{ + Uid: UserId, + }, + UploadType: uploadType, + ContentType: contentType, + File: file, + FileSize: fileHeader.Size, + FileExt: fileExt, + }, nil +} + +func (b *privBinding) BindDownloadAttachmentPrecheck(c *gin.Context) (*web.DownloadAttachmentPrecheckReq, mir.Error) { + user, exist := base.UserFrom(c) + if !exist { + return nil, xerror.UnauthorizedAuthNotExist + } + return &web.DownloadAttachmentPrecheckReq{ + BaseInfo: web.BaseInfo{ + User: user, + }, + ContentID: convert.StrTo(c.Query("id")).MustInt64(), + }, nil +} + +func (b *privBinding) BindDownloadAttachment(c *gin.Context) (*web.DownloadAttachmentReq, mir.Error) { + user, exist := base.UserFrom(c) + if !exist { + return nil, xerror.UnauthorizedAuthNotExist + } + return &web.DownloadAttachmentReq{ + BaseInfo: web.BaseInfo{ + User: user, + }, + ContentID: convert.StrTo(c.Query("id")).MustInt64(), + }, nil +} + +func (s *privBinding) BindCreateTweet(c *gin.Context) (*web.CreateTweetReq, mir.Error) { + v := &web.CreateTweetReq{} + err := s.BindAny(c, v) + v.ClientIP = c.ClientIP() + return v, err +} + func (s *privSrv) Chain() gin.HandlersChain { return gin.HandlersChain{chain.JWT(), chain.Priv()} } -func newPrivSrv() api.Priv { - return &privSrv{} +func (s *privSrv) UploadAttachment(req *web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) { + defer req.File.Close() + + // 生成随机路径 + randomPath := uuid.Must(uuid.NewV4()).String() + ossSavePath := req.UploadType + "/" + generatePath(randomPath[:8]) + "/" + randomPath[9:] + req.FileExt + objectUrl, err := s.oss.PutObject(ossSavePath, req.File, req.FileSize, req.ContentType, false) + if err != nil { + logrus.Errorf("oss.putObject err: %s", err) + return nil, _errFileUploadFailed + } + + // 构造附件Model + attachment := &core.Attachment{ + UserID: req.Uid, + FileSize: req.FileSize, + Content: objectUrl, + Type: _uploadAttachmentTypeMap[req.UploadType], + } + if attachment.Type == core.AttachmentTypeImage { + var src image.Image + src, err = imaging.Decode(req.File) + if err == nil { + attachment.ImgWidth, attachment.ImgHeight = getImageSize(src.Bounds()) + } + } + attachment, err = s.Ds.CreateAttachment(attachment) + if err != nil { + logrus.Errorf("Ds.CreateAttachment err: %s", err) + return nil, _errFileUploadFailed + } + + return &web.UploadAttachmentResp{ + UserID: req.Uid, + FileSize: req.FileSize, + ImgWidth: attachment.ImgWidth, + ImgHeight: attachment.ImgHeight, + Type: attachment.Type, + Content: attachment.Content, + }, nil +} + +func (s *privSrv) DownloadAttachmentPrecheck(req *web.DownloadAttachmentPrecheckReq) (*web.DownloadAttachmentPrecheckResp, mir.Error) { + content, err := s.Ds.GetPostContentByID(req.ContentID) + if err != nil { + logrus.Errorf("Ds.GetPostContentByID err: %s", err) + return nil, _errInvalidDownloadReq + } + resp := &web.DownloadAttachmentPrecheckResp{true} + if content.Type == core.ContentTypeChargeAttachment { + tweet, err := s.GetTweetBy(content.PostID) + if err != nil { + logrus.Errorf("get tweet err: %v", err) + return nil, _errInvalidDownloadReq + } + // 发布者或管理员免费下载 + if tweet.UserID == req.User.ID || req.User.IsAdmin { + return resp, nil + } + // 检测是否有购买记录 + resp.Paid = s.checkPostAttachmentIsPaid(req.ContentID, req.User.ID) + } + return resp, nil +} + +func (s *privSrv) DownloadAttachment(req *web.DownloadAttachmentReq) (*web.DownloadAttachmentResp, mir.Error) { + content, err := s.Ds.GetPostContentByID(req.ContentID) + if err != nil { + logrus.Errorf("s.GetPostContentByID err: %v", err) + return nil, _errInvalidDownloadReq + } + // 收费附件 + if content.Type == core.ContentTypeChargeAttachment { + post, err := s.GetTweetBy(content.PostID) + if err != nil { + logrus.Errorf("s.GetTweetBy err: %v", err) + return nil, xerror.ServerError + } + paidFlag := false + // 发布者或管理员免费下载 或者 检测是否有购买记录 + if post.UserID == req.User.ID || req.User.IsAdmin || s.checkPostAttachmentIsPaid(post.ID, req.User.ID) { + paidFlag = true + } + // 未购买,则尝试购买 + if !paidFlag { + err := s.buyPostAttachment(&core.Post{ + Model: &core.Model{ + ID: post.ID, + }, + UserID: post.UserID, + AttachmentPrice: post.AttachmentPrice, + }, req.User) + if err != nil { + return nil, err + } + } + } + // 签发附件下载链接 + objectKey := s.oss.ObjectKey(content.Content) + signedURL, err := s.oss.SignURL(objectKey, 60) + if err != nil { + logrus.Errorf("client.SignURL err: %v", err) + return nil, _errDownloadReqError + } + return &web.DownloadAttachmentResp{ + SignedURL: signedURL, + }, nil +} + +func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp, xerr mir.Error) { + var mediaContents []string + defer func() { + if xerr != nil { + deleteOssObjects(s.oss, mediaContents) + } + }() + + contents, err := persistMediaContents(s.oss, req.Contents) + if err != nil { + return nil, _errCreatePostFailed + } + mediaContents = contents + tags := tagsFrom(req.Tags) + post := &core.Post{ + UserID: req.User.ID, + Tags: strings.Join(tags, ","), + IP: req.ClientIP, + IPLoc: util.GetIPLoc(req.ClientIP), + AttachmentPrice: req.AttachmentPrice, + Visibility: req.Visibility, + } + post, err = s.Ds.CreatePost(post) + if err != nil { + logrus.Errorf("Ds.CreatePost err: %s", err) + return nil, _errCreatePostFailed + } + + // 创建推文内容 + for _, item := range req.Contents { + if err := item.Check(s.Ds); err != nil { + // 属性非法 + logrus.Infof("contents check err: %s", err) + continue + } + if item.Type == core.ContentTypeAttachment && req.AttachmentPrice > 0 { + item.Type = core.ContentTypeChargeAttachment + } + postContent := &core.PostContent{ + PostID: post.ID, + UserID: req.User.ID, + Content: item.Content, + Type: item.Type, + Sort: item.Sort, + } + if _, err = s.Ds.CreatePostContent(postContent); err != nil { + logrus.Infof("Ds.CreatePostContent err: %s", err) + return nil, _errCreateCommentFailed + } + } + + // 私密推文不创建标签与用户提醒 + if post.Visibility != core.PostVisitPrivate { + // 创建标签 + for _, t := range tags { + tag := &core.Tag{ + UserID: req.User.ID, + Tag: t, + } + s.Ds.CreateTag(tag) + } + + // 创建用户消息提醒 + for _, u := range req.Users { + user, err := s.Ds.GetUserByUsername(u) + if err != nil || user.ID == req.User.ID { + continue + } + + // 创建消息提醒 + // TODO: 优化消息提醒处理机制 + go s.Ds.CreateMessage(&core.Message{ + SenderUserID: req.User.ID, + ReceiverUserID: user.ID, + Type: core.MsgTypePost, + Brief: "在新发布的泡泡动态中@了你", + PostID: post.ID, + }) + } + } + // 推送Search + go s.PushPostToSearch(post) + + formatedPosts, err := s.Ds.RevampPosts([]*core.PostFormated{post.Format()}) + if err != nil { + logrus.Infof("Ds.RevampPosts err: %s", err) + return nil, _errCreatePostFailed + } + return (*web.CreateTweetResp)(formatedPosts[0]), nil +} + +func (s *privSrv) DeleteTweet(req *web.DeleteTweetReq) mir.Error { + if req.User == nil { + return _errNoPermission + } + post, err := s.Ds.GetPostByID(req.ID) + if err != nil { + logrus.Errorf("Ds.GetPostByID err: %s", err) + return _errGetPostFailed + } + if post.UserID != req.User.ID && !req.User.IsAdmin { + return _errNoPermission + } + mediaContents, err := s.Ds.DeletePost(post) + if err != nil { + logrus.Errorf("Ds.DeletePost delete post failed: %s", err) + return _errDeletePostFailed + } + // 删除推文的媒体内容 + deleteOssObjects(s.oss, mediaContents) + // 删除索引 + s.DeleteSearchPost(post) + if err != nil { + logrus.Errorf("s.DeleteSearchPost failed: %s", err) + return _errDeletePostFailed + } + return nil +} + +func (s *privSrv) CollectionTweet(req *web.CollectionTweetReq) (*web.CollectionTweetResp, mir.Error) { + status := false + collection, err := s.Ds.GetUserPostCollection(req.ID, req.Uid) + if err != nil { + // 创建Star + if _, xerr := s.createPostCollection(req.ID, req.Uid); xerr != nil { + return nil, xerr + } + status = true + } else { + // 取消Star + if xerr := s.deletePostCollection(collection); xerr != nil { + return nil, xerr + } + } + return &web.CollectionTweetResp{ + Status: status, + }, nil +} + +func (s *privSrv) StarTweet(req *web.StarTweetReq) (*web.StarTweetResp, mir.Error) { + status := false + star, err := s.Ds.GetUserPostStar(req.ID, req.Uid) + if err != nil { + // 创建Star + if _, xerr := s.createPostStar(req.ID, req.Uid); xerr != nil { + return nil, xerr + } + status = true + } else { + // 取消Star + if xerr := s.deletePostStar(star); xerr != nil { + return nil, xerr + } + } + return &web.StarTweetResp{ + Status: status, + }, nil +} + +func (s *privSrv) VisiblePost(req *web.VisiblePostReq) (*web.VisiblePostResp, mir.Error) { + if req.Visibility >= core.PostVisitInvalid { + return nil, xerror.InvalidParams + } + post, err := s.Ds.GetPostByID(req.User.ID) + if err != nil { + return nil, _errVisblePostFailed + } + if xerr := checkPermision(req.User, post.UserID); xerr != nil { + return nil, xerr + } + if err = s.Ds.VisiblePost(post, req.Visibility); err != nil { + logrus.Warnf("update post failure: %v", err) + return nil, _errVisblePostFailed + } + + // 推送Search + post.Visibility = req.Visibility + go s.PushPostToSearch(post) + + return &web.VisiblePostResp{ + Visibility: req.Visibility, + }, nil +} + +func (s *privSrv) StickTweet(req *web.StickTweetReq) (*web.StickTweetResp, mir.Error) { + post, err := s.Ds.GetPostByID(req.ID) + if err != nil { + logrus.Errorf("Ds.GetPostByID err: %v\n", err) + return nil, _errStickPostFailed + } + + if !req.User.IsAdmin { + return nil, _errNoPermission + } + if err = s.Ds.StickPost(post); err != nil { + return nil, _errStickPostFailed + } + return &web.StickTweetResp{ + StickStatus: 1 - post.IsTop, + }, nil +} + +func (s *privSrv) LockTweet(req *web.LockTweetReq) (*web.LockTweetResp, mir.Error) { + post, err := s.Ds.GetPostByID(req.ID) + if err != nil { + return nil, _errLockPostFailed + } + + if post.UserID != req.User.ID && !req.User.IsAdmin { + return nil, _errNoPermission + } + + if err := s.Ds.LockPost(post); err != nil { + return nil, _errLockPostFailed + } + return &web.LockTweetResp{ + LockStatus: 1 - post.IsLock, + }, nil +} + +func (s *privSrv) createPostStar(postID, userID int64) (*core.PostStar, mir.Error) { + // 加载Post + post, err := s.Ds.GetPostByID(postID) + if err != nil { + return nil, xerror.ServerError + } + + // 私密post不可操作 + if post.Visibility == core.PostVisitPrivate { + return nil, _errNoPermission + } + + star, err := s.Ds.CreatePostStar(postID, userID) + if err != nil { + return nil, xerror.ServerError + } + + // 更新Post点赞数 + post.UpvoteCount++ + s.Ds.UpdatePost(post) + + // 更新索引 + s.PushPostToSearch(post) + + return star, nil +} + +func (s *privSrv) deletePostStar(star *core.PostStar) mir.Error { + err := s.Ds.DeletePostStar(star) + if err != nil { + return xerror.ServerError + } + // 加载Post + post, err := s.Ds.GetPostByID(star.PostID) + if err != nil { + return xerror.ServerError + } + + // 私密post不可操作 + if post.Visibility == core.PostVisitPrivate { + return _errNoPermission + } + + // 更新Post点赞数 + post.UpvoteCount-- + s.Ds.UpdatePost(post) + + // 更新索引 + s.PushPostToSearch(post) + + return nil +} + +func (s *privSrv) createPostCollection(postID, userID int64) (*core.PostCollection, mir.Error) { + // 加载Post + post, err := s.Ds.GetPostByID(postID) + if err != nil { + return nil, xerror.ServerError + } + + // 私密post不可操作 + if post.Visibility == core.PostVisitPrivate { + return nil, _errNoPermission + } + + collection, err := s.Ds.CreatePostCollection(postID, userID) + if err != nil { + return nil, xerror.ServerError + } + + // 更新Post点赞数 + post.CollectionCount++ + s.Ds.UpdatePost(post) + + // 更新索引 + s.PushPostToSearch(post) + + return collection, nil +} + +func (s *privSrv) deletePostCollection(collection *core.PostCollection) mir.Error { + err := s.Ds.DeletePostCollection(collection) + if err != nil { + return xerror.ServerError + } + // 加载Post + post, err := s.Ds.GetPostByID(collection.PostID) + if err != nil { + return xerror.ServerError + } + + // 私密post不可操作 + if post.Visibility == core.PostVisitPrivate { + return _errNoPermission + } + + // 更新Post点赞数 + post.CollectionCount-- + s.Ds.UpdatePost(post) + + // 更新索引 + s.PushPostToSearch(post) + + return nil +} + +func (s *privSrv) checkPostAttachmentIsPaid(postID, userID int64) bool { + bill, err := s.Ds.GetPostAttatchmentBill(postID, userID) + return err == nil && bill.Model != nil && bill.ID > 0 +} + +func (s *privSrv) buyPostAttachment(post *core.Post, user *core.User) mir.Error { + if user.Balance < post.AttachmentPrice { + return _errInsuffientDownloadMoney + } + // 执行购买 + if err := s.Ds.HandlePostAttachmentBought(post, user); err != nil { + logrus.Errorf("Ds.HandlePostAttachmentBought err: %s", err) + return xerror.ServerError + } + return nil +} + +func newPrivSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Priv { + return &privSrv{ + DaoServant: s, + oss: oss, + } } func newPrivBinding() api.PrivBinding { diff --git a/internal/servants/web/utils.go b/internal/servants/web/utils.go index 1b5a05d7..2481b36d 100644 --- a/internal/servants/web/utils.go +++ b/internal/servants/web/utils.go @@ -1,13 +1,21 @@ +// Copyright 2022 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package web import ( + "image" "strings" "unicode/utf8" "github.com/alimy/mir/v3" "github.com/gofrs/uuid" "github.com/rocboss/paopao-ce/internal/core" + "github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/pkg/util" + "github.com/rocboss/paopao-ce/pkg/xerror" + "github.com/sirupsen/logrus" ) // checkPassword 密码检查 @@ -45,3 +53,93 @@ func deleteOssObjects(oss core.ObjectStorageService, mediaContents []string) { oss.DeleteObject(oss.ObjectKey(mediaContents[0])) } } + +// persistMediaContents 获取媒体内容并持久化 +func persistMediaContents(oss core.ObjectStorageService, contents []*web.PostContentItem) (items []string, err error) { + items = make([]string, 0, len(contents)) + for _, item := range contents { + switch item.Type { + case core.ContentTypeImage, + core.ContentTypeVideo, + core.ContentTypeAudio, + core.ContentTypeAttachment, + core.ContentTypeChargeAttachment: + items = append(items, item.Content) + if err != nil { + continue + } + if err = oss.PersistObject(oss.ObjectKey(item.Content)); err != nil { + logrus.Errorf("service.persistMediaContents failed: %s", err) + } + } + } + return +} + +func fileCheck(uploadType string, size int64) mir.Error { + if uploadType != "public/video" && + uploadType != "public/image" && + uploadType != "public/avatar" && + uploadType != "attachment" { + return xerror.InvalidParams + } + if size > 1024*1024*100 { + return _errFileInvalidSize.WithDetails("最大允许100MB") + } + return nil +} + +func getFileExt(s string) (string, mir.Error) { + switch s { + case "image/png": + return ".png", nil + case "image/jpg": + return ".jpg", nil + case "image/jpeg": + return ".jpeg", nil + case "image/gif": + return ".gif", nil + case "video/mp4": + return ".mp4", nil + case "video/quicktime": + return ".mov", nil + case "application/zip": + return ".zip", nil + default: + return "", _errFileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型") + } +} + +func generatePath(s string) string { + n := len(s) + if n <= 2 { + return s + } + return generatePath(s[:n-2]) + "/" + s[n-2:] +} + +func getImageSize(img image.Rectangle) (int, int) { + b := img.Bounds() + width := b.Max.X + height := b.Max.Y + return width, height +} + +func tagsFrom(originTags []string) []string { + tags := make([]string, 0, len(originTags)) + for _, tag := range originTags { + // TODO: 优化tag有效性检测 + if tag = strings.TrimSpace(tag); len(tag) > 0 { + tags = append(tags, tag) + } + } + return tags +} + +// checkPermision 检查是否拥有者或管理员 +func checkPermision(user *core.User, targetUserId int64) mir.Error { + if user == nil || (user.ID != targetUserId && !user.IsAdmin) { + return _errNoPermission + } + return nil +} diff --git a/internal/servants/web/web.go b/internal/servants/web/web.go index f9f3127b..ee74eecf 100644 --- a/internal/servants/web/web.go +++ b/internal/servants/web/web.go @@ -17,17 +17,17 @@ import ( // RouteWeb register web route func RouteWeb(e *gin.Engine) { - ts := dao.TweetSearchService() oss := dao.ObjectStorageService() ds := &base.DaoServant{ Redis: conf.Redis, Ds: dao.DataService(), + Ts: dao.TweetSearchService(), } // aways register servants api.RegisterAdminServant(e, newAdminSrv(ds), newAdminBinding(), newAdminRender()) - api.RegisterCoreServant(e, newCoreSrv(ds, ts, oss), newCoreBinding(), newCoreRender()) + api.RegisterCoreServant(e, newCoreSrv(ds, oss), newCoreBinding(), newCoreRender()) api.RegisterLooseServant(e, newLooseSrv(), newLooseBinding(), newLooseRender()) - api.RegisterPrivServant(e, newPrivSrv(), newPrivBinding(), newPrivRender()) + api.RegisterPrivServant(e, newPrivSrv(ds, oss), newPrivBinding(), newPrivRender()) api.RegisterPubServant(e, newPubSrv(), newPubBinding(), newPubRender()) // regster servants if needed by configure cfg.In(cfg.Actions{ diff --git a/internal/servants/web/xerror.go b/internal/servants/web/xerror.go index 544053b2..4de0f4b7 100644 --- a/internal/servants/web/xerror.go +++ b/internal/servants/web/xerror.go @@ -76,4 +76,8 @@ var ( _errDeleteFriendFailed = xerror.NewError(80006, "删除好友失败") _errGetContactsFailed = xerror.NewError(80007, "获取联系人列表失败") _errNoActionToSelf = xerror.NewError(80008, "不允许对自己操作") + + _errFileUploadFailed = xerror.NewError(10200, "文件上传失败") + _errFileInvalidExt = xerror.NewError(10201, "文件类型不合法") + _errFileInvalidSize = xerror.NewError(10202, "文件大小超限") ) diff --git a/mirc/web/v1/priv.go b/mirc/web/v1/priv.go index 86321a4e..f06d6c8d 100644 --- a/mirc/web/v1/priv.go +++ b/mirc/web/v1/priv.go @@ -16,34 +16,34 @@ type Priv struct { Group Group `mir:"v1"` // UploadAttachment 上传资源 - UploadAttachment func(Post) `mir:"/attachment"` + UploadAttachment func(Post, web.UploadAttachmentReq) web.UploadAttachmentResp `mir:"/attachment"` // DownloadAttachmentPrecheck 下载资源预检 - DownloadAttachmentPrecheck func(Get) `mir:"/attachment/precheck"` + DownloadAttachmentPrecheck func(Get, web.DownloadAttachmentPrecheckReq) web.DownloadAttachmentPrecheckResp `mir:"/attachment/precheck"` // DownloadAttachment 下载资源 - DownloadAttachment func(Get) `mir:"/attachment"` + DownloadAttachment func(Get, web.DownloadAttachmentReq) web.DownloadAttachmentResp `mir:"/attachment"` // CreateTweet 发布动态 CreateTweet func(Post, web.CreateTweetReq) web.CreateTweetResp `mir:"/post"` // DeleteTweet 删除动态 - DeleteTweet func(Delete) `mir:"/post"` + DeleteTweet func(Delete, web.DeleteTweetReq) `mir:"/post"` // StarTweet 动态点赞操作 - StarTweet func(Post) `mir:"/post/start"` + StarTweet func(Post, web.StarTweetReq) web.StarTweetResp `mir:"/post/start"` // CollectionTweet 动态收藏操作 - CollectionTweet func(Post) `mir:"/post/collection"` + CollectionTweet func(Post, web.CollectionTweetReq) web.CollectionTweetResp `mir:"/post/collection"` // LockTweet 锁定动态 - LockTweet func(Post) `mir:"/post/lock"` + LockTweet func(Post, web.LockTweetReq) web.LockTweetResp `mir:"/post/lock"` // StickTweet 置顶动态 - StickTweet func(Post) `mir:"/post/stick"` + StickTweet func(Post, web.StickTweetReq) web.StickTweetResp `mir:"/post/stick"` // VisiblePost 修改动态可见度 - VisiblePost func(Post) `mir:"/post/visibility"` + VisiblePost func(Post, web.VisiblePostReq) web.VisiblePostResp `mir:"/post/visibility"` // CreateTweetComment 发布动态评论 CreateComment func(Post) `mir:"/post/comment"`