support set visibility(public/private/friend) for post

pull/106/head
alimy 2 years ago
parent 087c6d26ff
commit 6f355c10b7

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

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

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

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

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

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

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

@ -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)
}
// 鉴权路由组

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

@ -390,3 +390,7 @@ func GetSuggestTags(keyword string) ([]string, error) {
return ts, nil
}
func IsFriend(userId, friendId int64) bool {
return ds.IsFriend(userId, friendId)
}

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

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

@ -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,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<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 +297,8 @@ const fileQueue = ref<UploadFileInfo[]>([]);
const imageContents = ref<Item.CommentItemProps[]>([]);
const videoContents = ref<Item.CommentItemProps[]>([]);
const attachmentContents = ref<Item.AttachmentProps[]>([]);
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;
}
</style>

@ -157,7 +157,9 @@ declare module NetParams {
/** 艾特用户列表 */
users: string[],
/** 附件价格 */
attachment_price: number
attachment_price: number,
/** 可见性 0公开 1私密 2好友可见 */
visibility: 0 | 1 | 2
}
interface PostDeletePost {

@ -161,6 +161,8 @@ declare module Item {
contents: PostItemProps[],
/** 标签列表 */
tags: { [key: string]: number } | string,
/** 可见性 0公开 1私密 2好友可见 */
visibility: 0 | 1 | 2,
/** 是否锁定 */
is_lock: number,
/** 是否置顶 */

Loading…
Cancel
Save