optimize tweet delete logic

pull/155/head
alimy 3 years ago
parent 783eaa1543
commit ffb2bac3e0

@ -7,6 +7,9 @@ import (
// ObjectStorageService storage service interface that implement base AliOSS、MINIO or other
type ObjectStorageService interface {
PutObject(objectKey string, reader io.Reader, objectSize int64, contentType string) (string, error)
DeleteObject(objectKey string) error
DeleteObjcets(objectKeys []string) error
IsObjectExist(objectKey string) (bool, error)
SignURL(objectKey string, expiredInSec int64) (string, error)
ObjectURL(objetKey string) string
ObjectKey(cUrl string) string

@ -25,7 +25,7 @@ type TweetService interface {
type TweetManageService interface {
CreateAttachment(attachment *model.Attachment) (*model.Attachment, error)
CreatePost(post *model.Post) (*model.Post, error)
DeletePost(post *model.Post) error
DeletePost(post *model.Post) ([]string, error)
LockPost(post *model.Post) error
StickPost(post *model.Post) error
VisiblePost(post *model.Post, visibility model.PostVisibleT) error

@ -170,12 +170,85 @@ func (s *tweetManageServant) CreatePost(post *model.Post) (*model.Post, error) {
return p, nil
}
func (s *tweetManageServant) DeletePost(post *model.Post) error {
if err := post.Delete(s.db); err != nil {
return err
func (s *tweetManageServant) DeletePost(post *model.Post) ([]string, error) {
var mediaContents []string
postId := post.ID
postContent := &model.PostContent{}
err := s.db.Transaction(
func(tx *gorm.DB) error {
if contents, err := postContent.MediaContentsByPostId(tx, postId); err == nil {
mediaContents = contents
} else {
return err
}
// 删推文
if err := (post).Delete(tx); err != nil {
return err
}
// 删内容
if err := postContent.DeleteByPostId(tx, postId); err != nil {
return err
}
// 删评论
if contents, err := s.deleteCommentByPostId(tx, postId); err == nil {
mediaContents = append(mediaContents, contents...)
} else {
return err
}
if tags := strings.Split(post.Tags, ","); len(tags) > 0 {
// 删tag宽松处理错误有错误不会回滚
deleteTags(tx, tags)
}
return nil
},
)
if err != nil {
return nil, err
}
s.cacheIndex.SendAction(core.IdxActDeletePost, post)
return nil
return mediaContents, nil
}
func (s *tweetManageServant) deleteCommentByPostId(db *gorm.DB, postId int64) ([]string, error) {
comment := &model.Comment{}
commentContent := &model.CommentContent{}
// 获取推文的所有评论id
commentIds, err := comment.CommentIdsByPostId(db, postId)
if err != nil {
return nil, err
}
// 获取评论的媒体内容
mediaContents, err := commentContent.MediaContentsByCommentId(db, commentIds)
if err != nil {
return nil, err
}
// 删评论
if err = comment.DeleteByPostId(db, postId); err != nil {
return nil, err
}
// 删评论内容
if err = commentContent.DeleteByCommentIds(db, commentIds); err != nil {
return nil, err
}
// 删评论的评论
if err = (&model.CommentReply{}).DeleteByCommentIds(db, commentIds); err != nil {
return nil, err
}
return mediaContents, nil
}
func (s *tweetManageServant) LockPost(post *model.Post) error {

@ -32,6 +32,24 @@ func deleteTag(db *gorm.DB, tag *model.Tag) error {
return tag.Update(db)
}
func deleteTags(db *gorm.DB, tags []string) error {
allTags, err := (&model.Tag{}).TagsFrom(db, tags)
if err != nil {
return err
}
for _, tag := range allTags {
tag.QuoteNum--
if tag.QuoteNum < 0 {
tag.QuoteNum = 0
}
// 宽松处理错误, 尽可能更新tag记录 只记录最后一次错误
if e := tag.Update(db); e != nil {
err = e
}
}
return err
}
// 根据IDs获取用户列表
func getUsersByIDs(db *gorm.DB, ids []int64) ([]*model.User, error) {
user := &model.User{}

@ -37,6 +37,19 @@ func (s *aliossServant) PutObject(objectKey string, reader io.Reader, objectSize
return s.domain + objectKey, nil
}
func (s *aliossServant) DeleteObject(objectKey string) error {
return s.bucket.DeleteObject(objectKey)
}
func (s *aliossServant) DeleteObjcets(objectKeys []string) error {
_, err := s.bucket.DeleteObjects(objectKeys)
return err
}
func (s *aliossServant) IsObjectExist(objectKey string) (bool, error) {
return s.bucket.IsObjectExist(objectKey)
}
func (s *aliossServant) SignURL(objectKey string, expiredInSec int64) (string, error) {
signedURL, err := s.bucket.SignURL(objectKey, oss.HTTPGet, expiredInSec)
if err != nil {

@ -57,6 +57,25 @@ func (s *localossServant) PutObject(objectKey string, reader io.Reader, objectSi
return s.domain + objectKey, nil
}
func (s *localossServant) DeleteObject(objectKey string) error {
return os.Remove(s.savePath + objectKey)
}
func (s *localossServant) DeleteObjcets(objectKeys []string) (err error) {
// 宽松处理删除动作尽可能删除所有objectKey如果出错只返回最后一个错误
for _, objectKey := range objectKeys {
if e := os.Remove(s.savePath + objectKey); e != nil {
err = e
}
}
return
}
func (s *localossServant) IsObjectExist(objectKey string) (bool, error) {
// TODO
return false, nil
}
func (s *localossServant) SignURL(objectKey string, expiredInSec int64) (string, error) {
if expiredInSec < 0 {
return "", fmt.Errorf("invalid expires: %d, expires must bigger than 0", expiredInSec)

@ -43,6 +43,37 @@ func (s *minioServant) PutObject(objectKey string, reader io.Reader, objectSize
return s.domain + objectKey, nil
}
func (s *minioServant) DeleteObject(objectKey string) error {
return s.client.RemoveObject(context.Background(), s.bucket, objectKey, minio.RemoveObjectOptions{ForceDelete: true})
}
func (s *minioServant) DeleteObjcets(objectKeys []string) (err error) {
objectsCh := make(chan minio.ObjectInfo, len(objectKeys))
resCh := s.client.RemoveObjects(context.Background(), s.bucket, objectsCh, minio.RemoveObjectsOptions{})
for _, objectKey := range objectKeys {
objectsCh <- minio.ObjectInfo{
Key: objectKey,
}
}
// 宽松处理所有错误,只记录最后一次发生的错误
for result := range resCh {
if result.Err != nil {
err = result.Err
}
}
return
}
func (s *minioServant) IsObjectExist(objectKey string) (bool, error) {
_, err := s.client.StatObject(context.Background(), s.bucket, objectKey, minio.StatObjectOptions{})
if err != nil {
return false, err
}
return true, nil
}
func (s *minioServant) SignURL(objectKey string, expiredInSec int64) (string, error) {
// TODO: Set request parameters for content-disposition.
reqParams := make(url.Values)

@ -108,7 +108,19 @@ func (c *Comment) Create(db *gorm.DB) (*Comment, error) {
}
func (c *Comment) Delete(db *gorm.DB) error {
return db.Model(&Comment{}).Where("id = ? AND is_del = ?", c.Model.ID, 0).Updates(map[string]interface{}{
return db.Model(c).Where("id = ?", c.Model.ID).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
}
func (c *Comment) CommentIdsByPostId(db *gorm.DB, postId int64) (ids []int64, err error) {
err = db.Model(c).Where("post_id = ?", postId).Select("id").Find(&ids).Error
return
}
func (c *Comment) DeleteByPostId(db *gorm.DB, postId int64) error {
return db.Model(c).Where("post_id = ?", postId).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error

@ -1,6 +1,10 @@
package model
import "gorm.io/gorm"
import (
"time"
"gorm.io/gorm"
)
type CommentContent struct {
*Model
@ -41,3 +45,15 @@ func (c *CommentContent) Create(db *gorm.DB) (*CommentContent, error) {
return c, err
}
func (c *CommentContent) MediaContentsByCommentId(db *gorm.DB, commentIds []int64) (contents []string, err error) {
err = db.Model(c).Where("comment_id IN ? AND type = ?", commentIds, CONTENT_TYPE_IMAGE).Select("content").Find(&contents).Error
return
}
func (c *CommentContent) DeleteByCommentIds(db *gorm.DB, commentIds []int64) error {
return db.Model(c).Where("comment_id IN ?", commentIds).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
}

@ -101,3 +101,10 @@ func (c *CommentReply) Delete(db *gorm.DB) error {
"is_del": 1,
}).Error
}
func (c *CommentReply) DeleteByCommentIds(db *gorm.DB, commentIds []int64) error {
return db.Model(c).Where("comment_id IN ?", commentIds).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
}

@ -91,7 +91,7 @@ func (p *Post) Create(db *gorm.DB) (*Post, error) {
}
func (s *Post) Delete(db *gorm.DB) error {
return db.Model(&Post{}).Where("id = ? AND is_del = ?", s.Model.ID, 0).Updates(map[string]interface{}{
return db.Model(s).Where("id = ?", s.Model.ID).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error

@ -1,6 +1,10 @@
package model
import "gorm.io/gorm"
import (
"time"
"gorm.io/gorm"
)
// 类型1标题2文字段落3图片地址4视频地址5语音地址6链接地址7附件资源
@ -17,6 +21,14 @@ const (
CONTENT_TYPE_CHARGE_ATTACHMENT
)
var mediaContentType = []PostContentT{
CONTENT_TYPE_IMAGE,
CONTENT_TYPE_VIDEO,
CONTENT_TYPE_AUDIO,
CONTENT_TYPE_ATTACHMENT,
CONTENT_TYPE_CHARGE_ATTACHMENT,
}
type PostContent struct {
*Model
PostID int64 `json:"post_id"`
@ -33,6 +45,18 @@ type PostContentFormated struct {
Sort int64 `json:"sort"`
}
func (p *PostContent) DeleteByPostId(db *gorm.DB, postId int64) error {
return db.Model(p).Where("post_id = ?", postId).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
}
func (p *PostContent) MediaContentsByPostId(db *gorm.DB, postId int64) (contents []string, err error) {
err = db.Model(p).Where("post_id = ? AND type IN ?", postId, mediaContentType).Select("content").Find(&contents).Error
return
}
func (p *PostContent) Create(db *gorm.DB) (*PostContent, error) {
err := db.Create(&p).Error

@ -1,6 +1,10 @@
package model
import "gorm.io/gorm"
import (
"time"
"gorm.io/gorm"
)
type Tag struct {
*Model
@ -56,6 +60,13 @@ func (t *Tag) Update(db *gorm.DB) error {
return db.Model(&Tag{}).Where("id = ? AND is_del = ?", t.Model.ID, 0).Save(t).Error
}
func (t *Tag) Delete(db *gorm.DB) error {
return db.Model(t).Where("id = ?", t.Model.ID).Updates(map[string]interface{}{
"deleted_on": time.Now().Unix(),
"is_del": 1,
}).Error
}
func (t *Tag) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*Tag, error) {
var tags []*Tag
var err error
@ -79,3 +90,8 @@ func (t *Tag) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*
return tags, nil
}
func (t *Tag) TagsFrom(db *gorm.DB, tags []string) (res []*Tag, err error) {
err = db.Where("tag IN ?", tags).Find(&res).Error
return
}

@ -92,22 +92,13 @@ func DeletePost(c *gin.Context) {
return
}
user, _ := c.Get("USER")
// 获取Post
postFormated, err := service.GetPost(param.ID)
if err != nil {
logrus.Errorf("service.GetPost err: %v\n", err)
response.ToErrorResponse(errcode.GetPostFailed)
return
}
if postFormated.UserID != user.(*model.User).ID && !user.(*model.User).IsAdmin {
user, exist := userFrom(c)
if !exist {
response.ToErrorResponse(errcode.NoPermission)
return
}
err = service.DeletePost(param.ID)
err := service.DeletePost(user, param.ID)
if err != nil {
logrus.Errorf("service.DeletePost err: %v\n", err)
response.ToErrorResponse(errcode.DeletePostFailed)

@ -180,23 +180,36 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos
return post, nil
}
func DeletePost(id int64) error {
post, _ := ds.GetPostByID(id)
// tag删除
tags := strings.Split(post.Tags, ",")
for _, t := range tags {
tag := &model.Tag{
Tag: t,
}
ds.DeleteTag(tag)
func DeletePost(user *model.User, id int64) *errcode.Error {
if user == nil {
return errcode.NoPermission
}
err := ds.DeletePost(post)
post, err := ds.GetPostByID(id)
if err != nil {
return errcode.GetPostFailed
}
if post.UserID != user.ID && !user.IsAdmin {
return errcode.NoPermission
}
mediaContents, err := ds.DeletePost(post)
if err != nil {
return err
logrus.Errorf("service.DeletePost delete post failed: %s", err)
return errcode.DeletePostFailed
}
// 删除推文的媒体内容, 宽松处理错误(就是不处理)
mediaContentsSize := len(mediaContents)
if mediaContentsSize > 1 {
objectKeys := make([]string, 0, mediaContentsSize)
for _, cUrl := range mediaContents {
objectKeys = append(objectKeys, oss.ObjectKey(cUrl))
}
// TODO: 优化处理尽量使用channel传递objectKeys使用可控数量的Goroutine集中处理object删除动作后续完善
go oss.DeleteObjcets(objectKeys)
} else if mediaContentsSize == 1 {
oss.DeleteObject(oss.ObjectKey(mediaContents[0]))
}
// 删除索引

@ -9,11 +9,13 @@ import (
var (
ds core.DataService
ts core.TweetSearchService
oss core.ObjectStorageService
DisablePhoneVerify bool
)
func Initialize() {
ds = dao.NewDataService()
ts = dao.NewTweetSearchService()
oss = dao.NewObjectStorageService()
DisablePhoneVerify = !conf.CfgIf("Sms")
}

Loading…
Cancel
Save