diff --git a/internal/core/core.go b/internal/core/core.go index f815525b..10ae82bf 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -36,6 +36,7 @@ type DataService interface { StickPost(post *model.Post) 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 +71,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..886cdb42 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) 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_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/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..39bc5844 100644 --- a/internal/model/post.go +++ b/internal/model/post.go @@ -7,20 +7,30 @@ import ( "gorm.io/gorm" ) +// PostVisibleT 可访问类型,0公开,1私密,2好友 +type PostVisibleT int + +const ( + PostVisitPublic PostVisibleT = iota + PostVisitPrivate + PostVisitFriend +) + 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 +41,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"` diff --git a/internal/routers/api/post.go b/internal/routers/api/post.go index 8b4f3703..c1452b8f 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) diff --git a/internal/routers/api/user.go b/internal/routers/api/user.go index 94b3bcb9..f7ca4633 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) } diff --git a/internal/routers/router.go b/internal/routers/router.go index 2a478664..9465ba4b 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) } // 鉴权路由组 diff --git a/internal/service/post.go b/internal/service/post.go index 00bc494c..b65ff0d9 100644 --- a/internal/service/post.go +++ b/internal/service/post.go @@ -36,6 +36,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 { @@ -92,6 +93,7 @@ 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 { @@ -325,7 +327,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) { @@ -411,6 +413,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() @@ -447,6 +454,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, @@ -470,7 +478,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) @@ -483,9 +493,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 { @@ -508,6 +520,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..5b4edfc4 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -390,3 +390,7 @@ func GetSuggestTags(keyword string) ([]string, error) { return ts, nil } + +func IsFriend(userId, friendId int64) bool { + return ds.IsFriend(userId, friendId) +} 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/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/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/components/compose.vue b/web/src/components/compose.vue index 8d5d8771..90961614 100644 --- a/web/src/components/compose.vue +++ b/web/src/components/compose.vue @@ -141,6 +141,19 @@ + + + +
@@ -200,6 +213,19 @@
+ +
+ + + + + +
@@ -242,11 +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'; + + const emit = defineEmits<{ (e: 'post-success', post: Item.PostProps): void; }>(); @@ -257,8 +286,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 +297,8 @@ const fileQueue = ref([]); const imageContents = ref([]); const videoContents = ref([]); const attachmentContents = ref([]); +const visitType = ref(0) +const visibilities = [{value: 0, label: "公开"}, {value: 1, label: "私密"}, {value: 2, label: "好友可见"}] const uploadGateway = import.meta.env.VITE_HOST + '/v1/attachment'; const uploadToken = ref(); @@ -277,6 +310,10 @@ const switchLink = () => { } }; +const switchEye = () => { + showEyeSet.value = !showEyeSet.value; +}; + // 加载at用户列表 const loadSuggestionUsers = debounce((k) => { getSuggestUsers({ @@ -513,6 +550,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 +559,7 @@ const submitPost = () => { // 置空 showLinkSet.value = false; + showEyeSet.value = false; uploadRef.value?.clear(); fileQueue.value = []; content.value = ''; @@ -528,6 +567,7 @@ const submitPost = () => { imageContents.value = []; videoContents.value = []; attachmentContents.value = []; + visitType.value = 0; }) .catch((err) => { submitting.value = false; @@ -597,4 +637,7 @@ onMounted(() => { overflow: hidden; } } +.eye-wrap { + margin-left: 64px; +} \ No newline at end of file diff --git a/web/src/types/NetParams.d.ts b/web/src/types/NetParams.d.ts index a26a2c6d..122e3189 100644 --- a/web/src/types/NetParams.d.ts +++ b/web/src/types/NetParams.d.ts @@ -157,7 +157,9 @@ declare module NetParams { /** 艾特用户列表 */ users: string[], /** 附件价格 */ - attachment_price: number + attachment_price: number, + /** 可见性 0公开 1私密 2好友可见 */ + visibility: 0 | 1 | 2 } interface PostDeletePost { diff --git a/web/src/types/item.d.ts b/web/src/types/item.d.ts index 6202929a..bc369592 100644 --- a/web/src/types/item.d.ts +++ b/web/src/types/item.d.ts @@ -161,6 +161,8 @@ declare module Item { contents: PostItemProps[], /** 标签列表 */ tags: { [key: string]: number } | string, + /** 可见性 0公开 1私密 2好友可见 */ + visibility: 0 | 1 | 2, /** 是否锁定 */ is_lock: number, /** 是否置顶 */