Merge pull request #97 from rocboss/feature/post-visibility

support set visibility(public/private/friend) for post
pull/115/head
ROC 2 years ago committed by GitHub
commit 071dfe5746
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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, // 禁用彩色打印
},
)

@ -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
}
}

@ -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"
}

@ -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
}

@ -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)
}

@ -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

@ -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)

@ -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

@ -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)
}

@ -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)

@ -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)
}

@ -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
}

@ -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()
}
}

@ -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"
}
}

@ -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
}

@ -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
}

@ -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, &param)
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)

@ -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
}

@ -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)

@ -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,

@ -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
}

@ -35,6 +35,7 @@ var (
InsuffientDownloadMoney = NewError(30009, "附件下载失败:账户资金不足")
DownloadExecFail = NewError(30010, "附件下载失败:扣费失败")
StickPostFailed = NewError(30011, "动态置顶失败")
VisblePostFailed = NewError(30012, "更新可见性失败")
GetCommentsFailed = NewError(40001, "获取评论列表失败")
CreateCommentFailed = NewError(40002, "评论发布失败")

@ -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;

@ -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;

@ -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;

@ -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
);

@ -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='冒泡/文章';
-- ----------------------------

@ -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
-- ----------------------------

@ -1,4 +1,4 @@
{
"version": "3",
"buildTime": "2022-06-08 23:29:43"
"version": "8",
"buildTime": "2022-06-13 17:16:22"
}

@ -108,6 +108,15 @@ export const stickPost = (data: NetParams.PostStickPost): Promise<NetReq.PostSti
});
};
/** 置顶/取消置顶动态 */
export const visibilityPost = (data: NetParams.PostVisibilityPost): Promise<NetReq.PostVisibilityPost> => {
return request({
method: 'post',
url: '/v1/post/visibility',
data
});
};
/** 发布动态评论 */
export const createComment = (data: NetParams.PostCreateComment): Promise<NetReq.PostCreateComment> => {
return request({

@ -141,6 +141,19 @@
</n-icon>
</template>
</n-button>
<n-button
quaternary
circle
type="primary"
@click.stop="switchEye"
>
<template #icon>
<n-icon size="20" color="var(--primary-color)">
<eye-outline />
</n-icon>
</template>
</n-button>
</div>
<div class="submit-wrap">
@ -200,6 +213,19 @@
<template #create-button-default> </template>
</n-dynamic-input>
</div>
<div class="eye-wrap" v-if="showEyeSet">
<n-radio-group v-model:value="visitType" name="radiogroup">
<n-space>
<n-radio
v-for="visit in visibilities"
:key="visit.value"
:value="visit.value"
:label="visit.label"
/>
</n-space>
</n-radio-group>
</div>
</div>
<div class="compose-wrap" v-else>
@ -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<MentionOption[]>([]);
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<UploadInst>();
const attachmentPrice = ref(0);
const uploadType = ref('public/image');
@ -266,6 +298,12 @@ const fileQueue = ref<UploadFileInfo[]>([]);
const imageContents = ref<Item.CommentItemProps[]>([]);
const videoContents = ref<Item.CommentItemProps[]>([]);
const attachmentContents = ref<Item.AttachmentProps[]>([]);
const visitType = ref<VisibilityEnum>(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;
}
</style>

@ -16,6 +16,33 @@
{{ post.user.nickname }}
</router-link>
<span class="username-wrap"> @{{ post.user.username }} </span>
<n-tag
v-if="post.is_top"
class="top-tag"
type="warning"
size="small"
round
>
</n-tag>
<n-tag
v-if="post.visibility == VisibilityEnum.PRIVATE"
class="top-tag"
type="error"
size="small"
round
>
</n-tag>
<n-tag
v-if="post.visibility == VisibilityEnum.FRIEND"
class="top-tag"
type="info"
size="small"
round
>
</n-tag>
</template>
<template #header-extra>
<div
@ -83,6 +110,21 @@
negative-text="取消"
@positive-click="execStickAction"
/>
<!-- -->
<n-modal
v-model:show="showVisibilityModal"
:mask-closable="false"
preset="dialog"
title="提示"
:content="
'' +
(tempVisibility == 0 ? '' : (tempVisibility == 1 ? '' : '')) +
''
"
positive-text="确认"
negative-text="取消"
@positive-click="execVisibilityAction"
/>
</template>
<div v-if="post.texts.length > 0">
<span
@ -174,7 +216,10 @@ import {
deletePost,
lockPost,
stickPost,
visibilityPost
} from '@/api/post';
import type { DropdownOption } from 'naive-ui';
import { VisibilityEnum } from '@/utils/IEnum';
const store = useStore();
const router = useRouter();
@ -189,7 +234,9 @@ const props = withDefaults(
const showDelModal = ref(false);
const showLockModal = ref(false);
const showStickModal = ref(false);
const showVisibilityModal = ref(false);
const loading = ref(false);
const tempVisibility = ref<VisibilityEnum>(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;
}

@ -27,6 +27,24 @@
>
</n-tag>
<n-tag
v-if="post.visibility == 1"
class="top-tag"
type="error"
size="small"
round
>
</n-tag>
<n-tag
v-if="post.visibility == 2"
class="top-tag"
type="info"
size="small"
round
>
</n-tag>
</template>
<template #header-extra>
<span class="timestamp">

@ -17,4 +17,4 @@ declare global {
$message: MessageApiInjection,
$store: any
}
}
}

@ -28,7 +28,7 @@ declare module Item {
/** 评论者UID */
user_id: number,
/** 类别1为标题2为文字段落3为图片地址4为视频地址5为语音地址6为链接地址 */
type: number,
type: import('@/utils/IEnum').CommentItemTypeEnum,
/** 内容 */
content: string,
/** 排序,越小越靠前 */
@ -111,7 +111,7 @@ declare module Item {
/** 内容ID */
id: number,
/** 类型1为标题2为文字段落3为图片地址4为视频地址5为语音地址6为链接地址7为附件资源8为收费资源 */
type: number,
type: import('@/utils/IEnum').PostItemTypeEnum,
/** POST ID */
post_id: number,
/** 内容 */
@ -161,6 +161,8 @@ declare module Item {
contents: PostItemProps[],
/** 标签列表 */
tags: { [key: string]: number } | string,
/** 可见性0为公开1为私密2为好友可见 */
visibility: import('@/utils/IEnum').VisibilityEnum,
/** 是否锁定 */
is_lock: number,
/** 是否置顶 */
@ -190,7 +192,7 @@ declare module Item {
interface MessageProps {
id: number,
/** 类型1为动态2为评论3为回复4为私信99为系统通知 */
type: 1 | 2 | 3 | 4 | 99,
type: import('@/utils/IEnum').MessageTypeEnum,
/** 摘要说明 */
brief: string,
/** 详细内容 */
@ -228,7 +230,7 @@ declare module Item {
interface AttachmentProps {
id: number,
/** 类别1为图片2为视频3为其他附件 */
type: 1 | 2 | 3,
type: import('@/utils/IEnum').AttachmentTypeEnum,
/** 发布者用户UID */
user_id: number,
/** 发布者用户数据 */
@ -287,4 +289,4 @@ declare module Item {
created_on: number
}
}
}

@ -124,6 +124,12 @@ declare module NetParams {
id: number
}
interface PostVisibilityPost {
id: number,
/** 可见性0为公开1为私密2为好友可见 */
visibility: import('@/utils/IEnum').VisibilityEnum
}
interface PostGetPostStar {
id: number
}
@ -157,7 +163,9 @@ declare module NetParams {
/** 艾特用户列表 */
users: string[],
/** 附件价格 */
attachment_price: number
attachment_price: number,
/** 可见性0为公开1为私密2为好友可见 */
visibility: import('@/utils/IEnum').VisibilityEnum
}
interface PostDeletePost {

@ -116,6 +116,11 @@ declare module NetReq {
top_status: 0 | 1
}
interface PostVisibilityPost {
/** 可见性0为公开1为私密2为好友可见 */
visibility_status: import('@/utils/IEnum').VisibilityEnum
}
interface PostGetPostStar {
status: boolean
}

@ -0,0 +1,69 @@
/** 动态内容类型枚举 */
export enum PostItemTypeEnum {
/** 标题 */
TITLE = 1,
/** 文字段落 */
TEXT = 2,
/** 图片地址 */
IMAGEURL = 3,
/** 视频地址 */
VIDEOURL = 4,
/** 音频地址 */
AUDIOURL = 5,
/** 链接地址 */
LINKURL = 6,
/** 附件资源 */
ATTACHMENT = 7,
/** 收费资源 */
CHARGEATTACHMENT = 8
}
/** 回复内容类型枚举 */
export enum CommentItemTypeEnum {
/** 标题 */
TITLE = 1,
/** 文字段落 */
TEXT = 2,
/** 图片地址 */
IMAGEURL = 3,
/** 视频地址 */
VIDEOURL = 4,
/** 音频地址 */
AUDIOURL = 5,
/** 链接地址 */
LINKURL = 6
}
/** 附件类型枚举 */
export enum AttachmentTypeEnum {
/** 图片 */
IMAGE = 1,
/** 视频 */
VIDEO = 2,
/** 其他 */
OTHER = 3
}
/** 消息类型枚举 */
export enum MessageTypeEnum {
/** 动态 */
POST = 1,
/** 评论 */
COMMENT = 2,
/** 回复 */
REPLY = 3,
/** 私信 */
PRIVATELETTER = 4,
/** 系统通知 */
SYSTEMNOTICE = 99
}
/** 动态可见度枚举 */
export enum VisibilityEnum {
/** 公开 */
PUBLIC,
/** 私密 */
PRIVATE,
/** 好友可见 */
FRIEND
}

@ -102,7 +102,7 @@
<n-form-item path="phone" label="手机号">
<n-input
:value="modelData.phone"
@update:value="(v) => (modelData.phone = v.trim())"
@update:value="(v: string) => (modelData.phone = v.trim())"
placeholder="请输入中国大陆手机号"
@keydown.enter.prevent
/>

Loading…
Cancel
Save