diff --git a/config.yaml.sample b/config.yaml.sample index 93506fb4..524cf9df 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -77,8 +77,8 @@ LocalOSS: # 本地文件OSS存储配置 Bucket: paopao Domain: 127.0.0.1:8008 Database: # Database通用配置 - LogLevel: 2 - TablePrefix: p_ + LogLevel: error # 日志级别 silent|error|warn|info + TablePrefix: p_ # 表名前缀 MySQL: # MySQL数据库 Username: paopao Password: paopao @@ -88,7 +88,7 @@ MySQL: # MySQL数据库 ParseTime: True MaxIdleConns: 10 MaxOpenConns: 30 -Postgres: +Postgres: # PostgreSQL数据库 User: paopao Password: paopao DBName: paopao diff --git a/internal/conf/db.go b/internal/conf/db.go index b517b1c0..649a0627 100644 --- a/internal/conf/db.go +++ b/internal/conf/db.go @@ -23,10 +23,10 @@ func newDBEngine() (*gorm.DB, error) { newLogger := logger.New( logrus.StandardLogger(), // io writer(日志输出的目标,前缀和日志包含的内容) logger.Config{ - SlowThreshold: time.Second, // 慢 SQL 阈值 - LogLevel: databaseSetting.LogLevel, // 日志级别 - IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 - Colorful: false, // 禁用彩色打印 + SlowThreshold: time.Second, // 慢 SQL 阈值 + LogLevel: databaseSetting.logLevel(), // 日志级别 + IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 + Colorful: false, // 禁用彩色打印 }, ) diff --git a/internal/conf/settting.go b/internal/conf/settting.go index 7ac6548e..33cb9131 100644 --- a/internal/conf/settting.go +++ b/internal/conf/settting.go @@ -80,7 +80,7 @@ type ZincSettingS struct { type DatabaseSetingS struct { TablePrefix string - LogLevel logger.LogLevel + LogLevel string } type MySQLSettingS struct { @@ -282,3 +282,18 @@ func (s PostgresSettingS) Dsn() string { } return strings.Join(params, " ") } + +func (s *DatabaseSetingS) logLevel() logger.LogLevel { + switch strings.ToLower(s.LogLevel) { + case "silent": + return logger.Silent + case "error": + return logger.Error + case "warn": + return logger.Warn + case "info": + return logger.Info + default: + return logger.Error + } +} diff --git a/internal/core/cache.go b/internal/core/cache.go index 08eae18f..07afbdb4 100644 --- a/internal/core/cache.go +++ b/internal/core/cache.go @@ -6,6 +6,7 @@ const ( IdxActUpdatePost IdxActDeletePost IdxActStickPost + IdxActVisiblePost ) type IndexActionT uint8 @@ -22,6 +23,8 @@ func (a IndexActionT) String() string { return "delete post" case IdxActStickPost: return "stick post" + case IdxActVisiblePost: + return "visible post" default: return "unknow action" } diff --git a/internal/core/core.go b/internal/core/core.go index f815525b..adcdf537 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -34,8 +34,10 @@ type DataService interface { DeletePost(post *model.Post) error LockPost(post *model.Post) error StickPost(post *model.Post) error + VisiblePost(post *model.Post, visibility model.PostVisibleT) error GetPostByID(id int64) (*model.Post, error) GetPosts(conditions *model.ConditionsT, offset, limit int) ([]*model.Post, error) + MergePosts(posts []*model.Post) ([]*model.PostFormated, error) GetPostCount(conditions *model.ConditionsT) (int64, error) UpdatePost(post *model.Post) error GetUserPostStar(postID, userID int64) (*model.PostStar, error) @@ -70,4 +72,6 @@ type DataService interface { GetLatestPhoneCaptcha(phone string) (*model.Captcha, error) UsePhoneCaptcha(captcha *model.Captcha) error SendPhoneCaptcha(phone string) error + + IsFriend(userID int64, friendID int64) bool } diff --git a/internal/core/index.go b/internal/core/index.go index afc02048..0188e6a2 100644 --- a/internal/core/index.go +++ b/internal/core/index.go @@ -5,5 +5,5 @@ import ( ) type IndexPostsService interface { - IndexPosts(offset int, limit int) ([]*model.PostFormated, error) + IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) } diff --git a/internal/core/search.go b/internal/core/search.go index b2033cd3..823da0d3 100644 --- a/internal/core/search.go +++ b/internal/core/search.go @@ -1,6 +1,7 @@ package core import ( + "github.com/rocboss/paopao-ce/internal/model" "github.com/rocboss/paopao-ce/pkg/zinc" ) @@ -12,8 +13,9 @@ const ( type SearchType string type QueryT struct { - Query string - Type SearchType + Query string + Visibility []model.PostVisibleT + Type SearchType } // SearchService search service interface that implement base zinc diff --git a/internal/dao/cache_index.go b/internal/dao/cache_index.go index b8626be9..adbd9fb2 100644 --- a/internal/dao/cache_index.go +++ b/internal/dao/cache_index.go @@ -14,7 +14,7 @@ var ( errNotExist = errors.New("index posts cache not exist") ) -func newSimpleCacheIndexServant(getIndexPosts func(offset, limit int) ([]*model.PostFormated, error)) *simpleCacheIndexServant { +func newSimpleCacheIndexServant(getIndexPosts indexPostsFunc) *simpleCacheIndexServant { s := conf.SimpleCacheIndexSetting cacheIndex := &simpleCacheIndexServant{ getIndexPosts: getIndexPosts, @@ -48,7 +48,7 @@ func newSimpleCacheIndexServant(getIndexPosts func(offset, limit int) ([]*model. return cacheIndex } -func (s *simpleCacheIndexServant) IndexPosts(offset int, limit int) ([]*model.PostFormated, error) { +func (s *simpleCacheIndexServant) IndexPosts(_userId int64, offset int, limit int) ([]*model.PostFormated, error) { posts := s.atomicIndex.Load().([]*model.PostFormated) end := offset + limit size := len(posts) @@ -78,7 +78,7 @@ func (s *simpleCacheIndexServant) startIndexPosts() { case <-s.checkTick.C: if len(s.indexPosts) == 0 { logrus.Debugf("index posts by checkTick") - if s.indexPosts, err = s.getIndexPosts(0, s.maxIndexSize); err == nil { + if s.indexPosts, err = s.getIndexPosts(0, 0, s.maxIndexSize); err == nil { s.atomicIndex.Store(s.indexPosts) } else { logrus.Errorf("get index posts err: %v", err) @@ -92,7 +92,12 @@ func (s *simpleCacheIndexServant) startIndexPosts() { } case action := <-s.indexActionCh: switch action { - case core.IdxActCreatePost, core.IdxActUpdatePost, core.IdxActDeletePost, core.IdxActStickPost: + // TODO: 这里列出来是因为后续可能会精细化处理每种情况 + case core.IdxActCreatePost, + core.IdxActUpdatePost, + core.IdxActDeletePost, + core.IdxActStickPost, + core.IdxActVisiblePost: // prevent many update post in least time if len(s.indexPosts) != 0 { logrus.Debugf("remove index posts by action %s", action) diff --git a/internal/dao/dao.go b/internal/dao/dao.go index 59634bfb..fb82b71c 100644 --- a/internal/dao/dao.go +++ b/internal/dao/dao.go @@ -32,8 +32,9 @@ type dataServant struct { zinc *zinc.ZincClient } +type indexPostsFunc func(int64, int, int) ([]*model.PostFormated, error) type simpleCacheIndexServant struct { - getIndexPosts func(offset, limit int) ([]*model.PostFormated, error) + getIndexPosts indexPostsFunc indexActionCh chan core.IndexActionT indexPosts []*model.PostFormated atomicIndex atomic.Value diff --git a/internal/dao/post.go b/internal/dao/post.go index 292e16f9..328de819 100644 --- a/internal/dao/post.go +++ b/internal/dao/post.go @@ -1,6 +1,7 @@ package dao import ( + "strings" "time" "github.com/rocboss/paopao-ce/internal/core" @@ -39,6 +40,46 @@ func (d *dataServant) StickPost(post *model.Post) error { return nil } +func (d *dataServant) VisiblePost(post *model.Post, visibility model.PostVisibleT) error { + oldVisibility := post.Visibility + post.Visibility = visibility + // TODO: 这个判断是否可以不要呢 + if oldVisibility == visibility { + return nil + } + // 私密推文 特殊处理 + if visibility == model.PostVisitPrivate { + // 强制取消置顶 + // TODO: 置顶推文用户是否有权设置成私密? 后续完善 + post.IsTop = 0 + } + db := d.engine.Begin() + err := post.Update(db) + if err != nil { + db.Rollback() + return err + } + + // tag处理 + tags := strings.Split(post.Tags, ",") + for _, t := range tags { + tag := &model.Tag{ + Tag: t, + } + // TODO: 暂时宽松不处理错误,这里或许可以有优化,后续完善 + if oldVisibility == model.PostVisitPrivate { + // 从私密转为非私密才需要重新创建tag + d.createTag(db, tag) + } else if visibility == model.PostVisitPrivate { + // 从非私密转为私密才需要删除tag + d.deleteTag(db, tag) + } + } + db.Commit() + d.indexActive(core.IdxActVisiblePost) + return nil +} + func (d *dataServant) GetPostByID(id int64) (*model.Post, error) { post := &model.Post{ Model: &model.Model{ @@ -79,7 +120,7 @@ func (d *dataServant) GetUserPostStars(userID int64, offset, limit int) ([]*mode } return star.List(d.engine, &model.ConditionsT{ - "ORDER": "id DESC", + "ORDER": d.engine.NamingStrategy.TableName("PostStar") + ".id DESC", }, offset, limit) } @@ -118,7 +159,7 @@ func (d *dataServant) GetUserPostCollections(userID int64, offset, limit int) ([ } return collection.List(d.engine, &model.ConditionsT{ - "ORDER": "id DESC", + "ORDER": d.engine.NamingStrategy.TableName("PostCollection") + ".id DESC", }, offset, limit) } diff --git a/internal/dao/post_index.go b/internal/dao/post_index.go index 0a184dd7..4353e938 100644 --- a/internal/dao/post_index.go +++ b/internal/dao/post_index.go @@ -6,15 +6,15 @@ import ( "github.com/sirupsen/logrus" ) -func (d *dataServant) IndexPosts(offset int, limit int) ([]*model.PostFormated, error) { +func (d *dataServant) IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) { if d.useCacheIndex { - if posts, err := d.cacheIndex.IndexPosts(offset, limit); err == nil { + if posts, err := d.cacheIndex.IndexPosts(userId, offset, limit); err == nil { logrus.Debugln("get index posts from cached") return posts, nil } } logrus.Debugf("get index posts from database but useCacheIndex: %t", d.useCacheIndex) - return d.getIndexPosts(offset, limit) + return d.getIndexPosts(userId, offset, limit) } func (d *dataServant) MergePosts(posts []*model.Post) ([]*model.PostFormated, error) { @@ -56,9 +56,11 @@ func (d *dataServant) MergePosts(posts []*model.Post) ([]*model.PostFormated, er return postsFormated, nil } -func (d *dataServant) getIndexPosts(offset int, limit int) ([]*model.PostFormated, error) { +// getIndexPosts _userId保留未来使用 +func (d *dataServant) getIndexPosts(_userId int64, offset int, limit int) ([]*model.PostFormated, error) { posts, err := (&model.Post{}).List(d.engine, &model.ConditionsT{ - "ORDER": "is_top DESC, latest_replied_on DESC", + "visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend}, + "ORDER": "is_top DESC, latest_replied_on DESC", }, offset, limit) if err != nil { logrus.Debugf("getIndexPosts err: %v", err) diff --git a/internal/dao/tag.go b/internal/dao/tag.go index 3f2765ae..d0935f7d 100644 --- a/internal/dao/tag.go +++ b/internal/dao/tag.go @@ -1,17 +1,32 @@ package dao -import "github.com/rocboss/paopao-ce/internal/model" +import ( + "github.com/rocboss/paopao-ce/internal/model" + "gorm.io/gorm" +) func (d *dataServant) CreateTag(tag *model.Tag) (*model.Tag, error) { + return d.createTag(d.engine, tag) +} + +func (d *dataServant) DeleteTag(tag *model.Tag) error { + return d.deleteTag(d.engine, tag) +} + +func (d *dataServant) GetTags(conditions *model.ConditionsT, offset, limit int) ([]*model.Tag, error) { + return (&model.Tag{}).List(d.engine, conditions, offset, limit) +} + +func (d *dataServant) createTag(db *gorm.DB, tag *model.Tag) (*model.Tag, error) { t, err := tag.Get(d.engine) if err != nil { tag.QuoteNum = 1 - return tag.Create(d.engine) + return tag.Create(db) } // 更新 t.QuoteNum++ - err = t.Update(d.engine) + err = t.Update(db) if err != nil { return nil, err @@ -20,16 +35,11 @@ func (d *dataServant) CreateTag(tag *model.Tag) (*model.Tag, error) { return t, nil } -func (d *dataServant) DeleteTag(tag *model.Tag) error { - tag, err := tag.Get(d.engine) +func (d *dataServant) deleteTag(db *gorm.DB, tag *model.Tag) error { + tag, err := tag.Get(db) if err != nil { return err } - tag.QuoteNum-- - return tag.Update(d.engine) -} - -func (d *dataServant) GetTags(conditions *model.ConditionsT, offset, limit int) ([]*model.Tag, error) { - return (&model.Tag{}).List(d.engine, conditions, offset, limit) + return tag.Update(db) } diff --git a/internal/dao/user.go b/internal/dao/user.go index ab0d72a8..ce07f5c9 100644 --- a/internal/dao/user.go +++ b/internal/dao/user.go @@ -158,3 +158,8 @@ func (d *dataServant) SendPhoneCaptcha(phone string) error { captchaModel.Create(d.engine) return nil } + +func (d *dataServant) IsFriend(_userID int64, _friendID int64) bool { + // TODO: you are friend in all now + return true +} diff --git a/internal/middleware/jwt.go b/internal/middleware/jwt.go index e8e99950..fc6db41e 100644 --- a/internal/middleware/jwt.go +++ b/internal/middleware/jwt.go @@ -74,3 +74,36 @@ func JWT() gin.HandlerFunc { c.Next() } } + +func JwtLoose() gin.HandlerFunc { + return func(c *gin.Context) { + token, exist := c.GetQuery("token") + if !exist { + token = c.GetHeader("Authorization") + // 验证前端传过来的token格式,不为空,开头为Bearer + if strings.HasPrefix(token, "Bearer ") { + // 验证通过,提取有效部分(除去Bearer) + token = token[7:] + } else { + c.Next() + } + } + if len(token) > 0 { + if claims, err := app.ParseToken(token); err == nil { + c.Set("UID", claims.UID) + c.Set("USERNAME", claims.Username) + // 加载用户信息 + user := &model.User{ + Model: &model.Model{ + ID: claims.UID, + }, + } + user, err := user.Get(conf.DBEngine) + if err == nil && (conf.JWTSetting.Issuer+":"+user.Salt) == claims.Issuer { + c.Set("USER", user) + } + } + } + c.Next() + } +} diff --git a/internal/model/post.go b/internal/model/post.go index 3ce80414..c8943464 100644 --- a/internal/model/post.go +++ b/internal/model/post.go @@ -7,20 +7,31 @@ import ( "gorm.io/gorm" ) +// PostVisibleT 可访问类型,0公开,1私密,2好友 +type PostVisibleT uint8 + +const ( + PostVisitPublic PostVisibleT = iota + PostVisitPrivate + PostVisitFriend + PostVisitInvalid +) + type Post struct { *Model - UserID int64 `json:"user_id"` - CommentCount int64 `json:"comment_count"` - CollectionCount int64 `json:"collection_count"` - UpvoteCount int64 `json:"upvote_count"` - IsTop int `json:"is_top"` - IsEssence int `json:"is_essence"` - IsLock int `json:"is_lock"` - LatestRepliedOn int64 `json:"latest_replied_on"` - Tags string `json:"tags"` - AttachmentPrice int64 `json:"attachment_price"` - IP string `json:"ip"` - IPLoc string `json:"ip_loc"` + UserID int64 `json:"user_id"` + CommentCount int64 `json:"comment_count"` + CollectionCount int64 `json:"collection_count"` + UpvoteCount int64 `json:"upvote_count"` + Visibility PostVisibleT `json:"visibility"` + IsTop int `json:"is_top"` + IsEssence int `json:"is_essence"` + IsLock int `json:"is_lock"` + LatestRepliedOn int64 `json:"latest_replied_on"` + Tags string `json:"tags"` + AttachmentPrice int64 `json:"attachment_price"` + IP string `json:"ip"` + IPLoc string `json:"ip_loc"` } type PostFormated struct { @@ -31,6 +42,7 @@ type PostFormated struct { CommentCount int64 `json:"comment_count"` CollectionCount int64 `json:"collection_count"` UpvoteCount int64 `json:"upvote_count"` + Visibility PostVisibleT `json:"visibility"` IsTop int `json:"is_top"` IsEssence int `json:"is_essence"` IsLock int `json:"is_lock"` @@ -56,6 +68,7 @@ func (p *Post) Format() *PostFormated { CommentCount: p.CommentCount, CollectionCount: p.CollectionCount, UpvoteCount: p.UpvoteCount, + Visibility: p.Visibility, IsTop: p.IsTop, IsEssence: p.IsEssence, IsLock: p.IsLock, @@ -144,3 +157,18 @@ func (p *Post) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { func (p *Post) Update(db *gorm.DB) error { return db.Model(&Post{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Save(p).Error } + +func (p PostVisibleT) String() string { + switch p { + case PostVisitPublic: + return "public" + case PostVisitPrivate: + return "private" + case PostVisitFriend: + return "friend" + case PostVisitInvalid: + return "invalid" + default: + return "unknow" + } +} diff --git a/internal/model/post_collection.go b/internal/model/post_collection.go index 2c7196ea..aec5d270 100644 --- a/internal/model/post_collection.go +++ b/internal/model/post_collection.go @@ -8,22 +8,26 @@ import ( type PostCollection struct { *Model + Post *Post `json:"-"` PostID int64 `json:"post_id"` UserID int64 `json:"user_id"` } func (p *PostCollection) Get(db *gorm.DB) (*PostCollection, error) { var star PostCollection + tn := db.NamingStrategy.TableName("PostCollection") + "." + if p.Model != nil && p.ID > 0 { - db = db.Where("id = ? AND is_del = ?", p.ID, 0) + db = db.Where(tn+"id = ? AND "+tn+"is_del = ?", p.ID, 0) } if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") err := db.First(&star).Error if err != nil { return &star, err @@ -33,13 +37,13 @@ func (p *PostCollection) Get(db *gorm.DB) (*PostCollection, error) { } func (p *PostCollection) Create(db *gorm.DB) (*PostCollection, error) { - err := db.Create(&p).Error + err := db.Omit("Post").Create(&p).Error return p, err } func (p *PostCollection) Delete(db *gorm.DB) error { - return db.Model(&PostCollection{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ + return db.Model(&PostCollection{}).Omit("Post").Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ "deleted_on": time.Now().Unix(), "is_del": 1, }).Error @@ -48,22 +52,25 @@ func (p *PostCollection) Delete(db *gorm.DB) error { func (p *PostCollection) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*PostCollection, error) { var collections []*PostCollection var err error + tn := db.NamingStrategy.TableName("PostCollection") + "." + if offset >= 0 && limit > 0 { db = db.Offset(offset).Limit(limit) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k == "ORDER" { db = db.Order(v) } else { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } - if err = db.Where("is_del = ?", 0).Find(&collections).Error; err != nil { + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") + if err = db.Where(tn+"is_del = ?", 0).Find(&collections).Error; err != nil { return nil, err } @@ -72,17 +79,21 @@ func (p *PostCollection) List(db *gorm.DB, conditions *ConditionsT, offset, limi func (p *PostCollection) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { var count int64 + tn := db.NamingStrategy.TableName("PostCollection") + "." + if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k != "ORDER" { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } + + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate) if err := db.Model(p).Count(&count).Error; err != nil { return 0, err } diff --git a/internal/model/post_star.go b/internal/model/post_star.go index 3f672664..d6c712ce 100644 --- a/internal/model/post_star.go +++ b/internal/model/post_star.go @@ -8,38 +8,40 @@ import ( type PostStar struct { *Model + Post *Post `json:"-"` PostID int64 `json:"post_id"` UserID int64 `json:"user_id"` } func (p *PostStar) Get(db *gorm.DB) (*PostStar, error) { var star PostStar + tn := db.NamingStrategy.TableName("PostStar") + "." + if p.Model != nil && p.ID > 0 { - db = db.Where("id = ? AND is_del = ?", p.ID, 0) + db = db.Where(tn+"id = ? AND "+tn+"is_del = ?", p.ID, 0) } if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } - err := db.First(&star).Error - if err != nil { - return &star, err + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") + if err := db.First(&star).Error; err != nil { + return nil, err } - return &star, nil } func (p *PostStar) Create(db *gorm.DB) (*PostStar, error) { - err := db.Create(&p).Error + err := db.Omit("Post").Create(&p).Error return p, err } func (p *PostStar) Delete(db *gorm.DB) error { - return db.Model(&PostStar{}).Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ + return db.Model(&PostStar{}).Omit("Post").Where("id = ? AND is_del = ?", p.Model.ID, 0).Updates(map[string]interface{}{ "deleted_on": time.Now().Unix(), "is_del": 1, }).Error @@ -48,44 +50,49 @@ func (p *PostStar) Delete(db *gorm.DB) error { func (p *PostStar) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]*PostStar, error) { var stars []*PostStar var err error + tn := db.NamingStrategy.TableName("PostStar") + "." + if offset >= 0 && limit > 0 { db = db.Offset(offset).Limit(limit) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k == "ORDER" { db = db.Order(v) } else { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } - if err = db.Where("is_del = ?", 0).Find(&stars).Error; err != nil { + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate).Order("Post.id DESC") + if err = db.Find(&stars).Error; err != nil { return nil, err } - return stars, nil } func (p *PostStar) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) { var count int64 + tn := db.NamingStrategy.TableName("PostStar") + "." + if p.PostID > 0 { - db = db.Where("post_id = ?", p.PostID) + db = db.Where(tn+"post_id = ?", p.PostID) } if p.UserID > 0 { - db = db.Where("user_id = ?", p.UserID) + db = db.Where(tn+"user_id = ?", p.UserID) } for k, v := range *conditions { if k != "ORDER" { - db = db.Where(k, v) + db = db.Where(tn+k, v) } } + + db = db.Joins("Post").Where("Post.visibility <> ?", PostVisitPrivate) if err := db.Model(p).Count(&count).Error; err != nil { return 0, err } - return count, nil } diff --git a/internal/routers/api/post.go b/internal/routers/api/post.go index 8b4f3703..af352afb 100644 --- a/internal/routers/api/post.go +++ b/internal/routers/api/post.go @@ -31,7 +31,8 @@ func GetPostList(c *gin.Context) { return } totalRows, _ := service.GetPostCount(&model.ConditionsT{ - "ORDER": "latest_replied_on DESC", + "visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend}, + "ORDER": "latest_replied_on DESC", }) response.ToResponseList(posts, totalRows) @@ -155,11 +156,16 @@ func PostStar(c *gin.Context) { star, err := service.GetPostStar(param.ID, userID.(int64)) if err != nil { // 创建Star - service.CreatePostStar(param.ID, userID.(int64)) + _, err = service.CreatePostStar(param.ID, userID.(int64)) status = true } else { // 取消Star - service.DeletePostStar(star) + err = service.DeletePostStar(star) + } + + if err != nil { + response.ToErrorResponse(errcode.NoPermission) + return } response.ToResponse(gin.H{ @@ -203,11 +209,16 @@ func PostCollection(c *gin.Context) { collection, err := service.GetPostCollection(param.ID, userID.(int64)) if err != nil { // 创建collection - service.CreatePostCollection(param.ID, userID.(int64)) + _, err = service.CreatePostCollection(param.ID, userID.(int64)) status = true } else { // 取消Star - service.DeletePostCollection(collection) + err = service.DeletePostCollection(collection) + } + + if err != nil { + response.ToErrorResponse(errcode.NoPermission) + return } response.ToResponse(gin.H{ @@ -287,6 +298,28 @@ func StickPost(c *gin.Context) { }) } +func VisiblePost(c *gin.Context) { + param := service.PostVisibilityReq{} + response := app.NewResponse(c) + valid, errs := app.BindAndValid(c, ¶m) + if !valid { + logrus.Errorf("app.BindAndValid errs: %v", errs) + response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...)) + return + } + + user, _ := userFrom(c) + if err := service.VisiblePost(user, param.ID, param.Visibility); err != nil { + logrus.Errorf("service.VisiblePost err: %v\n", err) + response.ToErrorResponse(err) + return + } + + response.ToResponse(gin.H{ + "visibility": param.Visibility, + }) +} + func GetPostTags(c *gin.Context) { param := service.PostTagsReq{} response := app.NewResponse(c) diff --git a/internal/routers/api/user.go b/internal/routers/api/user.go index 94b3bcb9..9529a805 100644 --- a/internal/routers/api/user.go +++ b/internal/routers/api/user.go @@ -313,13 +313,23 @@ func GetUserPosts(c *gin.Context) { return } - conditions := &model.ConditionsT{ - "user_id": user.ID, - "ORDER": "latest_replied_on DESC", + visibilities := []model.PostVisibleT{model.PostVisitPublic} + if u, exists := c.Get("USER"); exists { + self := u.(*model.User) + if self.ID == user.ID || self.IsAdmin { + visibilities = append(visibilities, model.PostVisitPrivate, model.PostVisitFriend) + } else if service.IsFriend(user.ID, self.ID) { + visibilities = append(visibilities, model.PostVisitFriend) + } + } + conditions := model.ConditionsT{ + "user_id": user.ID, + "visibility IN ?": visibilities, + "ORDER": "latest_replied_on DESC", } posts, err := service.GetPostList(&service.PostListReq{ - Conditions: conditions, + Conditions: &conditions, Offset: (app.GetPage(c) - 1) * app.GetPageSize(c), Limit: app.GetPageSize(c), }) @@ -328,7 +338,7 @@ func GetUserPosts(c *gin.Context) { response.ToErrorResponse(errcode.GetPostsFailed) return } - totalRows, _ := service.GetPostCount(conditions) + totalRows, _ := service.GetPostCount(&conditions) response.ToResponseList(posts, totalRows) } @@ -554,3 +564,12 @@ func GetUserWalletBills(c *gin.Context) { response.ToResponseList(bills, totalRows) } + +func userFrom(c *gin.Context) (*model.User, bool) { + if u, exists := c.Get("USER"); exists { + user, ok := u.(*model.User) + return user, ok + } + logrus.Debugln("user not exist") + return nil, false +} diff --git a/internal/routers/router.go b/internal/routers/router.go index 2a478664..54ffb90d 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -69,9 +69,13 @@ func NewRouter() *gin.Engine { // 获取用户基本信息 noAuthApi.GET("/user/profile", api.GetUserProfile) + } + // 宽松鉴权路由组 + looseApi := r.Group("/").Use(middleware.JwtLoose()) + { // 获取用户动态列表 - noAuthApi.GET("/user/posts", api.GetUserPosts) + looseApi.GET("/user/posts", api.GetUserPosts) } // 鉴权路由组 @@ -163,6 +167,9 @@ func NewRouter() *gin.Engine { // 置顶动态 privApi.POST("/post/stick", api.StickPost) + // 修改动态可见度 + privApi.POST("/post/visibility", api.VisiblePost) + // 发布动态评论 privApi.POST("/post/comment", api.CreatePostComment) diff --git a/internal/service/post.go b/internal/service/post.go index 224cd9cc..c2193eee 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -2,6 +2,7 @@ package service import ( "encoding/json" + "errors" "fmt" "math" "strings" @@ -11,6 +12,7 @@ import ( "github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/model" + "github.com/rocboss/paopao-ce/pkg/errcode" "github.com/rocboss/paopao-ce/pkg/util" "github.com/rocboss/paopao-ce/pkg/zinc" "github.com/sirupsen/logrus" @@ -36,6 +38,7 @@ type PostCreationReq struct { Tags []string `json:"tags" binding:"required"` Users []string `json:"users" binding:"required"` AttachmentPrice int64 `json:"attachment_price"` + Visibility model.PostVisibleT `json:"visibility"` } type PostDelReq struct { @@ -50,6 +53,11 @@ type PostStickReq struct { ID int64 `json:"id" binding:"required"` } +type PostVisibilityReq struct { + ID int64 `json:"id" binding:"required"` + Visibility model.PostVisibleT `json:"visibility"` +} + type PostStarReq struct { ID int64 `json:"id" binding:"required"` } @@ -94,8 +102,13 @@ func tagsFrom(originTags []string) []string { return tags } +// CreatePost 创建文章 +// TODO: maybe have bug need optimize for use transaction to create post func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Post, error) { ip := c.ClientIP() + if len(ip) == 0 { + ip = "未知" + } tags := tagsFrom(param.Tags) post := &model.Post{ @@ -104,21 +117,13 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos IP: ip, IPLoc: util.GetIPLoc(ip), AttachmentPrice: param.AttachmentPrice, + Visibility: param.Visibility, } post, err := ds.CreatePost(post) if err != nil { return nil, err } - // 创建标签 - for _, t := range tags { - tag := &model.Tag{ - UserID: userID, - Tag: t, - } - ds.CreateTag(tag) - } - for _, item := range param.Contents { if err = item.Check(); err != nil { // 属性非法 @@ -137,27 +142,43 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos Type: item.Type, Sort: item.Sort, } - ds.CreatePostContent(postContent) + if _, err := ds.CreatePostContent(postContent); err != nil { + return nil, err + } } - // 推送Search - go PushPostToSearch(post) - - // 创建用户消息提醒 - for _, u := range param.Users { - user, err := ds.GetUserByUsername(u) - if err != nil || user.ID == userID { - continue + // TODO: 目前非私密文章才能有如下操作,后续再优化 + if post.Visibility != model.PostVisitPrivate { + // 创建标签 + for _, t := range tags { + tag := &model.Tag{ + UserID: userID, + Tag: t, + } + if _, err := ds.CreateTag(tag); err != nil { + return nil, err + } } + // 创建用户消息提醒 + for _, u := range param.Users { + user, err := ds.GetUserByUsername(u) + if err != nil || user.ID == userID { + continue + } - // 创建消息提醒 - go ds.CreateMessage(&model.Message{ - SenderUserID: userID, - ReceiverUserID: user.ID, - Type: model.MESSAGE_POST, - Brief: "在新发布的泡泡动态中@了你", - PostID: post.ID, - }) + // 创建消息提醒 + // TODO: 优化消息提醒处理机制 + go ds.CreateMessage(&model.Message{ + SenderUserID: userID, + ReceiverUserID: user.ID, + Type: model.MESSAGE_POST, + Brief: "在新发布的泡泡动态中@了你", + PostID: post.ID, + }) + } + // 推送Search + // TODO: 优化推送文章到搜索的处理机制,最好使用通道channel传递文章,可以省goroutine + go PushPostToSearch(post) } return post, nil @@ -212,6 +233,45 @@ func StickPost(id int64) error { return nil } +func VisiblePost(user *model.User, postId int64, visibility model.PostVisibleT) *errcode.Error { + if visibility >= model.PostVisitInvalid { + return errcode.InvalidParams + } + + post, err := ds.GetPostByID(postId) + if err != nil { + return errcode.GetPostFailed + } + + if err := checkPermision(user, post.UserID); err != nil { + return err + } + + // 相同属性,不需要操作了 + oldVisibility := post.Visibility + if oldVisibility == visibility { + logrus.Infof("sample visibility no need operate postId: %d", postId) + return nil + } + + if err = ds.VisiblePost(post, visibility); err != nil { + logrus.Warnf("update post failure: %v", err) + return errcode.VisblePostFailed + } + + // 搜索处理 + if oldVisibility == model.PostVisitPrivate { + // 从私密转为非私密需要push + logrus.Debugf("visible post set to re-public to add search index: %d, visibility: %s", post.ID, visibility) + go PushPostToSearch(post) + } else if visibility == model.PostVisitPrivate { + // 从非私密转为私密需要删除索引 + logrus.Debugf("visible post set to private to delete search index: %d, visibility: %s", post.ID, visibility) + go DeleteSearchPost(post) + } + return nil +} + func GetPostStar(postID, userID int64) (*model.PostStar, error) { return ds.GetUserPostStar(postID, userID) } @@ -222,6 +282,12 @@ func CreatePostStar(postID, userID int64) (*model.PostStar, error) { if err != nil { return nil, err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return nil, errors.New("no permision") + } + star, err := ds.CreatePostStar(postID, userID) if err != nil { return nil, err @@ -247,6 +313,12 @@ func DeletePostStar(star *model.PostStar) error { if err != nil { return err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return errors.New("no permision") + } + // 更新Post点赞数 post.UpvoteCount-- ds.UpdatePost(post) @@ -267,6 +339,12 @@ func CreatePostCollection(postID, userID int64) (*model.PostCollection, error) { if err != nil { return nil, err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return nil, errors.New("no permision") + } + collection, err := ds.CreatePostCollection(postID, userID) if err != nil { return nil, err @@ -292,6 +370,12 @@ func DeletePostCollection(collection *model.PostCollection) error { if err != nil { return err } + + // 私密post不可操作 + if post.Visibility == model.PostVisitPrivate { + return errors.New("no permision") + } + // 更新Post点赞数 post.CollectionCount-- ds.UpdatePost(post) @@ -337,7 +421,7 @@ func GetPostContentByID(id int64) (*model.PostContent, error) { } func GetIndexPosts(offset int, limit int) ([]*model.PostFormated, error) { - return ds.IndexPosts(offset, limit) + return ds.IndexPosts(0, offset, limit) } func GetPostList(req *PostListReq) ([]*model.PostFormated, error) { @@ -423,6 +507,11 @@ func GetPostListFromSearchByQuery(query string, offset, limit int) ([]*model.Pos } func PushPostToSearch(post *model.Post) { + // TODO: 暂时不索引私密文章,后续再完善 + if post.Visibility == model.PostVisitPrivate { + return + } + indexName := conf.ZincSetting.Index postFormated := post.Format() @@ -459,6 +548,7 @@ func PushPostToSearch(post *model.Post) { "comment_count": post.CommentCount, "collection_count": post.CollectionCount, "upvote_count": post.UpvoteCount, + "visibility": post.Visibility, "is_top": post.IsTop, "is_essence": post.IsEssence, "content": contentFormated, @@ -482,7 +572,9 @@ func DeleteSearchPost(post *model.Post) error { func PushPostsToSearch(c *gin.Context) { if ok, _ := conf.Redis.SetNX(c, "JOB_PUSH_TO_SEARCH", 1, time.Hour).Result(); ok { splitNum := 1000 - totalRows, _ := GetPostCount(&model.ConditionsT{}) + totalRows, _ := GetPostCount(&model.ConditionsT{ + "visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend}, + }) pages := math.Ceil(float64(totalRows) / float64(splitNum)) nums := int(pages) @@ -495,9 +587,11 @@ func PushPostsToSearch(c *gin.Context) { data := []map[string]interface{}{} posts, _ := GetPostList(&PostListReq{ - Conditions: &model.ConditionsT{}, - Offset: i * splitNum, - Limit: splitNum, + Conditions: &model.ConditionsT{ + "visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend}, + }, + Offset: i * splitNum, + Limit: splitNum, }) for _, post := range posts { @@ -520,6 +614,7 @@ func PushPostsToSearch(c *gin.Context) { "comment_count": post.CommentCount, "collection_count": post.CollectionCount, "upvote_count": post.UpvoteCount, + "visibility": post.Visibility, "is_top": post.IsTop, "is_essence": post.IsEssence, "content": contentFormated, diff --git a/internal/service/user.go b/internal/service/user.go index 25f5a225..c4d0234e 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -274,21 +274,11 @@ func GetUserCollections(userID int64, offset, limit int) ([]*model.PostFormated, if err != nil { return nil, 0, err } - postIDs := []int64{} + var posts []*model.Post for _, collection := range collections { - postIDs = append(postIDs, collection.PostID) + posts = append(posts, collection.Post) } - - // 获取Posts - posts, err := ds.GetPosts(&model.ConditionsT{ - "id IN ?": postIDs, - "ORDER": "id DESC", - }, 0, 0) - if err != nil { - return nil, 0, err - } - - postsFormated, err := FormatPosts(posts) + postsFormated, err := ds.MergePosts(posts) if err != nil { return nil, 0, err } @@ -306,21 +296,12 @@ func GetUserStars(userID int64, offset, limit int) ([]*model.PostFormated, int64 if err != nil { return nil, 0, err } - postIDs := []int64{} - for _, star := range stars { - postIDs = append(postIDs, star.PostID) - } - // 获取Posts - posts, err := ds.GetPosts(&model.ConditionsT{ - "id IN ?": postIDs, - "ORDER": "id DESC", - }, 0, 0) - if err != nil { - return nil, 0, err + var posts []*model.Post + for _, star := range stars { + posts = append(posts, star.Post) } - - postsFormated, err := FormatPosts(posts) + postsFormated, err := ds.MergePosts(posts) if err != nil { return nil, 0, err } @@ -390,3 +371,15 @@ func GetSuggestTags(keyword string) ([]string, error) { return ts, nil } + +func IsFriend(userId, friendId int64) bool { + return ds.IsFriend(userId, friendId) +} + +// checkPermision 检查是否拥有者或管理员 +func checkPermision(user *model.User, targetUserId int64) *errcode.Error { + if user == nil || (user.ID != targetUserId && !user.IsAdmin) { + return errcode.NoPermission + } + return nil +} diff --git a/pkg/errcode/module_code.go b/pkg/errcode/module_code.go index 02976dc6..ea3b5e25 100644 --- a/pkg/errcode/module_code.go +++ b/pkg/errcode/module_code.go @@ -35,6 +35,7 @@ var ( InsuffientDownloadMoney = NewError(30009, "附件下载失败:账户资金不足") DownloadExecFail = NewError(30010, "附件下载失败:扣费失败") StickPostFailed = NewError(30011, "动态置顶失败") + VisblePostFailed = NewError(30012, "更新可见性失败") GetCommentsFailed = NewError(40001, "获取评论列表失败") CreateCommentFailed = NewError(40002, "评论发布失败") diff --git a/scripts/migration/mysql/001-2206131310.sql b/scripts/migration/mysql/001-2206131310.sql new file mode 100644 index 00000000..c654a3bb --- /dev/null +++ b/scripts/migration/mysql/001-2206131310.sql @@ -0,0 +1,9 @@ +-- ---------------------------- +-- Table p_post alter add visibility column +-- ---------------------------- +ALTER TABLE `p_post` ADD COLUMN `visibility` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '可见性 0公开 1私密 2好友可见'; + +-- ---------------------------- +-- Indexes structure for table p_post +-- ---------------------------- +CREATE INDEX `idx_visibility` ON `p_post` ( `visibility` ) USING BTREE; diff --git a/scripts/migration/paopao-mysql.sql b/scripts/migration/paopao-mysql.sql new file mode 100644 index 00000000..d78496d5 --- /dev/null +++ b/scripts/migration/paopao-mysql.sql @@ -0,0 +1,310 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for p_attachment +-- ---------------------------- +DROP TABLE IF EXISTS `p_attachment`; +CREATE TABLE `p_attachment` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `user_id` bigint unsigned NOT NULL DEFAULT '0', + `file_size` bigint unsigned NOT NULL, + `img_width` bigint unsigned NOT NULL DEFAULT '0', + `img_height` bigint unsigned NOT NULL DEFAULT '0', + `type` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '1图片,2视频,3其他附件', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=100041 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='附件'; + +-- ---------------------------- +-- Table structure for p_captcha +-- ---------------------------- +DROP TABLE IF EXISTS `p_captcha`; +CREATE TABLE `p_captcha` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '验证码ID', + `phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号', + `captcha` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '验证码', + `use_times` int unsigned NOT NULL DEFAULT '0' COMMENT '使用次数', + `expired_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '过期时间', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_phone` (`phone`) USING BTREE, + KEY `idx_expired_on` (`expired_on`) USING BTREE, + KEY `idx_use_times` (`use_times`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1021 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='手机验证码'; + +-- ---------------------------- +-- Table structure for p_comment +-- ---------------------------- +DROP TABLE IF EXISTS `p_comment`; +CREATE TABLE `p_comment` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '评论ID', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'POST ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `ip` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP地址', + `ip_loc` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP城市地址', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_post` (`post_id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6001736 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='评论'; + +-- ---------------------------- +-- Table structure for p_comment_content +-- ---------------------------- +DROP TABLE IF EXISTS `p_comment_content`; +CREATE TABLE `p_comment_content` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '内容ID', + `comment_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '评论ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '内容', + `type` tinyint unsigned NOT NULL DEFAULT '2' COMMENT '类型,1标题,2文字段落,3图片地址,4视频地址,5语音地址,6链接地址', + `sort` bigint unsigned NOT NULL DEFAULT '100' COMMENT '排序,越小越靠前', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_reply` (`comment_id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE, + KEY `idx_type` (`type`) USING BTREE, + KEY `idx_sort` (`sort`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=11001738 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='评论内容'; + +-- ---------------------------- +-- Table structure for p_comment_reply +-- ---------------------------- +DROP TABLE IF EXISTS `p_comment_reply`; +CREATE TABLE `p_comment_reply` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '回复ID', + `comment_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '评论ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `at_user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '@用户ID', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '内容', + `ip` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP地址', + `ip_loc` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP城市地址', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_comment` (`comment_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=12000015 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='评论回复'; + +-- ---------------------------- +-- Table structure for p_message +-- ---------------------------- +DROP TABLE IF EXISTS `p_message`; +CREATE TABLE `p_message` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '消息通知ID', + `sender_user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '发送方用户ID', + `receiver_user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '接收方用户ID', + `type` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '通知类型,1动态,2评论,3回复,4私信,99系统通知', + `brief` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '摘要说明', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '详细内容', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '动态ID', + `comment_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '评论ID', + `reply_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '回复ID', + `is_read` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否已读', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_receiver` (`receiver_user_id`) USING BTREE, + KEY `idx_is_read` (`is_read`) USING BTREE, + KEY `idx_type` (`type`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=16000033 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='消息通知'; + +-- ---------------------------- +-- Table structure for p_post +-- ---------------------------- +DROP TABLE IF EXISTS `p_post`; +CREATE TABLE `p_post` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主题ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `comment_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '评论数', + `collection_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '收藏数', + `upvote_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '点赞数', + `is_top` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否置顶', + `is_essence` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否精华', + `is_lock` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否锁定', + `latest_replied_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '最新回复时间', + `tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '标签', + `attachment_price` bigint unsigned NOT NULL DEFAULT '0' COMMENT '附件价格(分)', + `ip` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP地址', + `ip_loc` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP城市地址', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1080017989 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='冒泡/文章'; + +-- ---------------------------- +-- Table structure for p_post_attachment_bill +-- ---------------------------- +DROP TABLE IF EXISTS `p_post_attachment_bill`; +CREATE TABLE `p_post_attachment_bill` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '购买记录ID', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'POST ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `paid_amount` bigint unsigned NOT NULL DEFAULT '0' COMMENT '支付金额', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_post` (`post_id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=5000002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='冒泡/文章附件账单'; + +-- ---------------------------- +-- Table structure for p_post_collection +-- ---------------------------- +DROP TABLE IF EXISTS `p_post_collection`; +CREATE TABLE `p_post_collection` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '收藏ID', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'POST ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_post` (`post_id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6000012 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='冒泡/文章收藏'; + +-- ---------------------------- +-- Table structure for p_post_content +-- ---------------------------- +DROP TABLE IF EXISTS `p_post_content`; +CREATE TABLE `p_post_content` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '内容ID', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'POST ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `content` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '内容', + `type` tinyint unsigned NOT NULL DEFAULT '2' COMMENT '类型,1标题,2文字段落,3图片地址,4视频地址,5语音地址,6链接地址,7附件资源,8收费资源', + `sort` int unsigned NOT NULL DEFAULT '100' COMMENT '排序,越小越靠前', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_post` (`post_id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=180022546 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='冒泡/文章内容'; + +-- ---------------------------- +-- Table structure for p_post_star +-- ---------------------------- +DROP TABLE IF EXISTS `p_post_star`; +CREATE TABLE `p_post_star` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '收藏ID', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'POST ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_post` (`post_id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6000028 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='冒泡/文章点赞'; + +-- ---------------------------- +-- Table structure for p_tag +-- ---------------------------- +DROP TABLE IF EXISTS `p_tag`; +CREATE TABLE `p_tag` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '标签ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建者ID', + `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标签名', + `quote_num` bigint unsigned NOT NULL DEFAULT '0' COMMENT '引用数', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `idx_tag` (`tag`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE, + KEY `idx_num` (`quote_num`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=9000065 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='标签'; + +-- ---------------------------- +-- Table structure for p_user +-- ---------------------------- +DROP TABLE IF EXISTS `p_user`; +CREATE TABLE `p_user` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称', + `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名', + `phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号', + `password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'MD5密码', + `salt` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '盐值', + `status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态,1正常,2停用', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户头像', + `balance` bigint unsigned NOT NULL COMMENT '用户余额(分)', + `is_admin` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否管理员', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `idx_username` (`username`) USING BTREE, + KEY `idx_phone` (`phone`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=100058 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户'; + +-- ---------------------------- +-- Table structure for p_wallet_recharge +-- ---------------------------- +DROP TABLE IF EXISTS `p_wallet_recharge`; +CREATE TABLE `p_wallet_recharge` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '充值ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `amount` bigint NOT NULL DEFAULT '0' COMMENT '充值金额', + `trade_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '支付宝订单号', + `trade_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '交易状态', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE, + KEY `idx_trade_no` (`trade_no`) USING BTREE, + KEY `idx_trade_status` (`trade_status`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10023 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='钱包流水'; + +-- ---------------------------- +-- Table structure for p_wallet_statement +-- ---------------------------- +DROP TABLE IF EXISTS `p_wallet_statement`; +CREATE TABLE `p_wallet_statement` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '账单ID', + `user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '用户ID', + `change_amount` bigint NOT NULL DEFAULT '0' COMMENT '变动金额', + `balance_snapshot` bigint NOT NULL DEFAULT '0' COMMENT '资金快照', + `reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '变动原因', + `post_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '关联动态', + `created_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `modified_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '修改时间', + `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', + `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_user` (`user_id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=10010 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='钱包流水'; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/scripts/migration/paopao-sqlite3.sql b/scripts/migration/paopao-sqlite3.sql new file mode 100644 index 00000000..77882be2 --- /dev/null +++ b/scripts/migration/paopao-sqlite3.sql @@ -0,0 +1,309 @@ +PRAGMA foreign_keys = false; + +-- ---------------------------- +-- Table structure for p_attachment +-- ---------------------------- +DROP TABLE IF EXISTS "p_attachment"; +CREATE TABLE "p_attachment" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "file_size" integer NOT NULL, + "img_width" integer NOT NULL, + "img_height" integer NOT NULL, + "type" integer NOT NULL, + "content" text(255) NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_captcha +-- ---------------------------- +DROP TABLE IF EXISTS "p_captcha"; +CREATE TABLE "p_captcha" ( + "id" integer NOT NULL, + "phone" text(16) NOT NULL, + "captcha" text(16) NOT NULL, + "use_times" integer NOT NULL, + "expired_on" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_comment +-- ---------------------------- +DROP TABLE IF EXISTS "p_comment"; +CREATE TABLE "p_comment" ( + "id" integer NOT NULL, + "post_id" integer NOT NULL, + "user_id" integer NOT NULL, + "ip" text(64) NOT NULL, + "ip_loc" text(64) NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_comment_content +-- ---------------------------- +DROP TABLE IF EXISTS "p_comment_content"; +CREATE TABLE "p_comment_content" ( + "id" integer NOT NULL, + "comment_id" integer NOT NULL, + "user_id" integer NOT NULL, + "content" text(255) NOT NULL, + "type" integer NOT NULL, + "sort" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_comment_reply +-- ---------------------------- +DROP TABLE IF EXISTS "p_comment_reply"; +CREATE TABLE "p_comment_reply" ( + "id" integer NOT NULL, + "comment_id" integer NOT NULL, + "user_id" integer NOT NULL, + "at_user_id" integer NOT NULL, + "content" text(255) NOT NULL, + "ip" text(64) NOT NULL, + "ip_loc" text(64) NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_message +-- ---------------------------- +DROP TABLE IF EXISTS "p_message"; +CREATE TABLE "p_message" ( + "id" integer NOT NULL, + "sender_user_id" integer NOT NULL, + "receiver_user_id" integer NOT NULL, + "type" integer NOT NULL, + "brief" text(255) NOT NULL, + "content" text(255) NOT NULL, + "post_id" integer NOT NULL, + "comment_id" integer NOT NULL, + "reply_id" integer NOT NULL, + "is_read" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_post +-- ---------------------------- +DROP TABLE IF EXISTS "p_post"; +CREATE TABLE "p_post" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "comment_count" integer NOT NULL, + "collection_count" integer NOT NULL, + "upvote_count" integer NOT NULL, + "is_top" integer NOT NULL, + "is_essence" integer NOT NULL, + "is_lock" integer NOT NULL, + "latest_replied_on" integer NOT NULL, + "tags" text(255) NOT NULL, + "attachment_price" integer NOT NULL, + "ip" text(64) NOT NULL, + "ip_loc" text(64) NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_post_attachment_bill +-- ---------------------------- +DROP TABLE IF EXISTS "p_post_attachment_bill"; +CREATE TABLE "p_post_attachment_bill" ( + "id" integer NOT NULL, + "post_id" integer NOT NULL, + "user_id" integer NOT NULL, + "paid_amount" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_post_collection +-- ---------------------------- +DROP TABLE IF EXISTS "p_post_collection"; +CREATE TABLE "p_post_collection" ( + "id" integer NOT NULL, + "post_id" integer NOT NULL, + "user_id" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_post_content +-- ---------------------------- +DROP TABLE IF EXISTS "p_post_content"; +CREATE TABLE "p_post_content" ( + "id" integer NOT NULL, + "post_id" integer NOT NULL, + "user_id" integer NOT NULL, + "content" text(2000) NOT NULL, + "type" integer NOT NULL, + "sort" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_post_star +-- ---------------------------- +DROP TABLE IF EXISTS "p_post_star"; +CREATE TABLE "p_post_star" ( + "id" integer NOT NULL, + "post_id" integer NOT NULL, + "user_id" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_tag +-- ---------------------------- +DROP TABLE IF EXISTS "p_tag"; +CREATE TABLE "p_tag" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "tag" text(255) NOT NULL, + "quote_num" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_user +-- ---------------------------- +DROP TABLE IF EXISTS "p_user"; +CREATE TABLE "p_user" ( + "id" integer NOT NULL, + "nickname" text(32) NOT NULL, + "username" text(32) NOT NULL, + "phone" text(16) NOT NULL, + "password" text(32) NOT NULL, + "salt" text(16) NOT NULL, + "status" integer NOT NULL, + "avatar" text(255) NOT NULL, + "balance" integer NOT NULL, + "is_admin" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_wallet_recharge +-- ---------------------------- +DROP TABLE IF EXISTS "p_wallet_recharge"; +CREATE TABLE "p_wallet_recharge" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "amount" integer NOT NULL, + "trade_no" text(64) NOT NULL, + "trade_status" text(32) NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Table structure for p_wallet_statement +-- ---------------------------- +DROP TABLE IF EXISTS "p_wallet_statement"; +CREATE TABLE "p_wallet_statement" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "change_amount" integer NOT NULL, + "balance_snapshot" integer NOT NULL, + "reason" text(255) NOT NULL, + "post_id" integer NOT NULL, + "created_on" integer NOT NULL, + "modified_on" integer NOT NULL, + "deleted_on" integer NOT NULL, + "is_del" integer NOT NULL, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Indexes structure for table p_attachment +-- ---------------------------- +CREATE INDEX "main"."idx_user" +ON "p_attachment" ( + "user_id" ASC +); + +-- ---------------------------- +-- Indexes structure for table p_captcha +-- ---------------------------- +CREATE INDEX "main"."idx_expired_on" +ON "p_captcha" ( + "expired_on" ASC +); +CREATE INDEX "main"."idx_phone" +ON "p_captcha" ( + "phone" ASC +); +CREATE INDEX "main"."idx_use_times" +ON "p_captcha" ( + "use_times" ASC +); + +-- ---------------------------- +-- Indexes structure for table p_comment +-- ---------------------------- +CREATE INDEX "main"."idx_post" +ON "p_comment" ( + "post_id" ASC +); + +PRAGMA foreign_keys = true; diff --git a/scripts/migration/sqlite3/001-2206131310.sql b/scripts/migration/sqlite3/001-2206131310.sql new file mode 100644 index 00000000..63657693 --- /dev/null +++ b/scripts/migration/sqlite3/001-2206131310.sql @@ -0,0 +1,12 @@ +-- ---------------------------- +-- Table p_post alter add visibility column +-- ---------------------------- +ALTER TABLE `p_post` ADD COLUMN `visibility` integer NOT NULL DEFAULT '0'; + +-- ---------------------------- +-- Indexes structure for table p_post +-- ---------------------------- +CREATE INDEX "main"."idx_visibility" +ON "p_post" ( + "visibility" ASC +); diff --git a/scripts/paopao-mysql.sql b/scripts/paopao-mysql.sql index d78496d5..2fb8c258 100644 --- a/scripts/paopao-mysql.sql +++ b/scripts/paopao-mysql.sql @@ -137,6 +137,7 @@ CREATE TABLE `p_post` ( `comment_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '评论数', `collection_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '收藏数', `upvote_count` bigint unsigned NOT NULL DEFAULT '0' COMMENT '点赞数', + `visibility` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '可见性 0公开 1私密 2好友可见', `is_top` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否置顶', `is_essence` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否精华', `is_lock` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否锁定', @@ -150,7 +151,8 @@ CREATE TABLE `p_post` ( `deleted_on` bigint unsigned NOT NULL DEFAULT '0' COMMENT '删除时间', `is_del` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除', PRIMARY KEY (`id`) USING BTREE, - KEY `idx_user` (`user_id`) USING BTREE + KEY `idx_user` (`user_id`) USING BTREE, + KEY `idx_visibility` (`visibility`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1080017989 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='冒泡/文章'; -- ---------------------------- diff --git a/scripts/paopao-sqlite3.sql b/scripts/paopao-sqlite3.sql index b1b49fe9..685539e7 100644 --- a/scripts/paopao-sqlite3.sql +++ b/scripts/paopao-sqlite3.sql @@ -1,16 +1,3 @@ -/* - Navicat Premium Data Transfer - - Source Server : paopao-ce - Source Server Type : SQLite - Source Server Version : 3035005 - Source Schema : main - - Target Server Type : SQLite - Target Server Version : 3035005 - File Encoding : 65001 -*/ - PRAGMA foreign_keys = false; -- ---------------------------- @@ -135,6 +122,7 @@ CREATE TABLE "p_post" ( "comment_count" integer NOT NULL, "collection_count" integer NOT NULL, "upvote_count" integer NOT NULL, + "visibility" integer NOT NULL, "is_top" integer NOT NULL, "is_essence" integer NOT NULL, "is_lock" integer NOT NULL, @@ -181,6 +169,14 @@ CREATE TABLE "p_post_collection" ( PRIMARY KEY ("id") ); +-- ---------------------------- +-- Indexes structure for table p_post +-- ---------------------------- +CREATE INDEX "main"."idx_visibility" +ON "p_post" ( + "visibility" ASC +); + -- ---------------------------- -- Table structure for p_post_content -- ---------------------------- diff --git a/web/build/info.json b/web/build/info.json index f430d3b0..290a7eb3 100644 --- a/web/build/info.json +++ b/web/build/info.json @@ -1,4 +1,4 @@ { - "version": "3", - "buildTime": "2022-06-08 23:29:43" + "version": "8", + "buildTime": "2022-06-13 17:16:22" } \ No newline at end of file diff --git a/web/src/api/post.ts b/web/src/api/post.ts index 7f2bc408..d562149d 100644 --- a/web/src/api/post.ts +++ b/web/src/api/post.ts @@ -108,6 +108,15 @@ export const stickPost = (data: NetParams.PostStickPost): Promise => { + return request({ + method: 'post', + url: '/v1/post/visibility', + data + }); +}; + /** 发布动态评论 */ export const createComment = (data: NetParams.PostCreateComment): Promise => { return request({ diff --git a/web/src/components/compose.vue b/web/src/components/compose.vue index 8d5d8771..ca4409b6 100644 --- a/web/src/components/compose.vue +++ b/web/src/components/compose.vue @@ -141,6 +141,19 @@ + + + +
@@ -200,6 +213,19 @@
+ +
+ + + + + +
@@ -242,10 +268,14 @@ import { VideocamOutline, AttachOutline, CompassOutline, + EyeOutline, } from '@vicons/ionicons5'; import { createPost } from '@/api/post'; import { parsePostTag } from '@/utils/content'; import type { MentionOption, UploadFileInfo, UploadInst } from 'naive-ui'; +import { VisibilityEnum, PostItemTypeEnum } from '@/utils/IEnum'; + + const emit = defineEmits<{ (e: 'post-success', post: Item.PostProps): void; @@ -257,8 +287,10 @@ const optionsRef = ref([]); const loading = ref(false); const submitting = ref(false); const showLinkSet = ref(false); +const showEyeSet = ref(false); const content = ref(''); const links = ref([]); + const uploadRef = ref(); const attachmentPrice = ref(0); const uploadType = ref('public/image'); @@ -266,6 +298,12 @@ const fileQueue = ref([]); const imageContents = ref([]); const videoContents = ref([]); const attachmentContents = ref([]); +const visitType = ref(VisibilityEnum.PUBLIC); +const visibilities = [ + {value: VisibilityEnum.PUBLIC, label: "公开"} + , {value: VisibilityEnum.PRIVATE, label: "私密"} + , {value: VisibilityEnum.FRIEND, label: "好友可见"} +]; const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment'; const uploadToken = ref(); @@ -277,6 +315,10 @@ const switchLink = () => { } }; +const switchEye = () => { + showEyeSet.value = !showEyeSet.value; +}; + // 加载at用户列表 const loadSuggestionUsers = debounce((k) => { getSuggestUsers({ @@ -468,7 +510,7 @@ const submitPost = () => { contents.push({ content: content.value, - type: 2, // 文字 + type: PostItemTypeEnum.TEXT, // 文字 sort, }); @@ -476,7 +518,7 @@ const submitPost = () => { sort++; contents.push({ content: img.content, - type: 3, // 图片 + type: PostItemTypeEnum.IMAGEURL, // 图片 sort, }); }); @@ -484,7 +526,7 @@ const submitPost = () => { sort++; contents.push({ content: video.content, - type: 4, // 图片 + type: PostItemTypeEnum.VIDEOURL, // 视频 sort, }); }); @@ -492,7 +534,7 @@ const submitPost = () => { sort++; contents.push({ content: attachment.content, - type: 7, // 附件 + type: PostItemTypeEnum.ATTACHMENT, // 附件 sort, }); }); @@ -501,7 +543,7 @@ const submitPost = () => { sort++; contents.push({ content: link, - type: 6, // 链接 + type: PostItemTypeEnum.LINKURL, // 链接 sort, }); }); @@ -513,6 +555,7 @@ const submitPost = () => { tags: Array.from(new Set(tags)), users: Array.from(new Set(users)), attachment_price: +attachmentPrice.value * 100, + visibility: visitType.value }) .then((res) => { window.$message.success('发布成功'); @@ -521,6 +564,7 @@ const submitPost = () => { // 置空 showLinkSet.value = false; + showEyeSet.value = false; uploadRef.value?.clear(); fileQueue.value = []; content.value = ''; @@ -528,6 +572,7 @@ const submitPost = () => { imageContents.value = []; videoContents.value = []; attachmentContents.value = []; + visitType.value = VisibilityEnum.PUBLIC; }) .catch((err) => { submitting.value = false; @@ -597,4 +642,7 @@ onMounted(() => { overflow: hidden; } } +.eye-wrap { + margin-left: 64px; +} \ No newline at end of file diff --git a/web/src/components/post-detail.vue b/web/src/components/post-detail.vue index af283e9c..586255a1 100644 --- a/web/src/components/post-detail.vue +++ b/web/src/components/post-detail.vue @@ -16,6 +16,33 @@ {{ post.user.nickname }} @{{ post.user.username }} + + 置顶 + + + 私密 + + + 好友可见 +
(VisibilityEnum.PUBLIC); const emit = defineEmits<{ (e: 'reload'): void; @@ -238,7 +285,7 @@ const post = computed({ }); const adminOptions = computed(() => { - let options = [ + let options: DropdownOption[] = [ { label: '删除', key: 'delete', @@ -268,6 +315,34 @@ const adminOptions = computed(() => { }); } } + if (post.value.visibility === VisibilityEnum.PUBLIC) { + options.push({ + label: '公开', + key: 'vpublic', + children: [ + { label: '私密', key: 'vprivate' } + , { label: '好友可见', key: 'vfriend' } + ] + }) + } else if (post.value.visibility === VisibilityEnum.PRIVATE) { + options.push({ + label: '私密', + key: 'vprivate', + children: [ + { label: '公开', key: 'vpublic' } + , { label: '好友可见', key: 'vfriend' } + ] + }) + } else { + options.push({ + label: '好友可见', + key: 'vfriend', + children: [ + { label: '公开', key: 'vpublic' } + , { label: '私密', key: 'vprivate' } + ] + }) + } return options; }); @@ -306,16 +381,34 @@ const doClickText = (e: MouseEvent, id: number) => { goPostDetail(id); }; const handlePostAction = ( - item: 'delete' | 'lock' | 'unlock' | 'stick' | 'unstick' + item: 'delete' | 'lock' | 'unlock' | 'stick' | 'unstick' | 'vpublic' | 'vprivate' | 'vfriend' ) => { - if (item === 'delete') { - showDelModal.value = true; - } - if (item === 'lock' || item === 'unlock') { - showLockModal.value = true; - } - if (item === 'stick' || item === 'unstick') { - showStickModal.value = true; + switch (item) { + case 'delete': + showDelModal.value = true; + break; + case 'lock': + case 'unlock': + showLockModal.value = true; + break; + case 'stick': + case 'unstick': + showStickModal.value = true; + break; + case 'vpublic': + tempVisibility.value = 0; + showVisibilityModal.value = true; + break; + case 'vprivate': + tempVisibility.value = 1; + showVisibilityModal.value = true; + break; + case 'vfriend': + tempVisibility.value = 2; + showVisibilityModal.value = true; + break; + default: + break; } }; const execDelAction = () => { @@ -366,6 +459,19 @@ const execStickAction = () => { loading.value = false; }); }; +const execVisibilityAction = () => { + visibilityPost({ + id: post.value.id, + visibility: tempVisibility.value + }) + .then((res) => { + emit('reload'); + window.$message.success('修改可见性成功'); + }) + .catch((err) => { + loading.value = false; + }); +}; const handlePostStar = () => { postStar({ id: post.value.id, @@ -450,7 +556,9 @@ onMounted(() => { font-size: 14px; opacity: 0.75; } - + .top-tag { + transform: scale(0.75); + } .options { opacity: 0.75; } diff --git a/web/src/components/post-item.vue b/web/src/components/post-item.vue index 06dfd018..b686d602 100644 --- a/web/src/components/post-item.vue +++ b/web/src/components/post-item.vue @@ -27,6 +27,24 @@ > 置顶 + + 私密 + + + 好友可见 +