Merge pull request #141 from alimy/pr-search-private

support search private tweet
pull/142/head
Michael Li 3 years ago committed by GitHub
commit 7c2ad0bd82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -293,7 +293,7 @@ release/paopao-ce --no-default-features --features sqlite3,localoss,loggerfile,r
`LocalOSS` 提供使用本地目录文件作为对象存储的功能,仅用于开发调试环境;
* 缓存: Redis/SimpleCacheIndex/BigCacheIndex
`SimpleCacheIndex` 提供简单的 广场推文列表 的缓存功能;
`BigCacheIndex` 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面;
`BigCacheIndex` 使用[BigCache](https://github.com/allegro/bigcache)缓存 广场推文列表,缓存每个用户每一页,简单做到千人千面(推荐使用)
* 搜索: Zinc/Meili
`Zinc` 基于[Zinc](https://github.com/zinclabs/zinc)搜索引擎提供推文搜索服务(目前状态: 稳定,推荐使用)
`Meili` 基于[Meilisearch](https://github.com/meilisearch/meilisearch)搜索引擎提供推文搜索服务(目前状态: 内测阶段);

@ -0,0 +1,117 @@
package core
import (
"github.com/rocboss/paopao-ce/internal/model"
"github.com/rocboss/paopao-ce/pkg/types"
)
const (
ActRegisterUser act = iota
ActCreatePublicTweet
ActCreatePublicAttachment
ActCreatePublicPicture
ActCreatePublicVideo
ActCreatePrivateTweet
ActCreatePrivateAttachment
ActCreatePrivatePicture
ActCreatePrivateVideo
ActCreateFriendTweet
ActCreateFriendAttachment
ActCreateFriendPicture
ActCreateFriendVideo
ActCreatePublicComment
ActCreatePublicPicureComment
ActCreateFriendComment
ActCreateFriendPicureComment
ActCreatePrivateComment
ActCreatePrivatePicureComment
ActStickTweet
ActTopTweet
ActLockTweet
ActVisibleTweet
ActDeleteTweet
ActCreateActivationCode
)
type act uint8
type FriendFilter map[int64]types.Empty
type Action struct {
Act act
UserId int64
}
type AuthorizationManageService interface {
IsAllow(user *model.User, action *Action) bool
GetFriendFilter(userId int64) FriendFilter
GetFriendIds(userId int64) []int64
}
func (f FriendFilter) IsFriend(userId int64) bool {
// _, yesno := f[userId]
// return yesno
// so, you are friend with all world now
return true
}
// IsAllow default true if user is admin
func (a act) IsAllow(user *model.User, userId int64, isFriend bool, isActivation bool) bool {
if user.IsAdmin {
return true
}
if user.ID == userId && isActivation {
switch a {
case ActCreatePublicTweet,
ActCreatePublicAttachment,
ActCreatePublicPicture,
ActCreatePublicVideo,
ActCreatePrivateTweet,
ActCreatePrivateAttachment,
ActCreatePrivatePicture,
ActCreatePrivateVideo,
ActCreateFriendTweet,
ActCreateFriendAttachment,
ActCreateFriendPicture,
ActCreateFriendVideo,
ActCreatePrivateComment,
ActCreatePrivatePicureComment,
ActStickTweet,
ActLockTweet,
ActVisibleTweet,
ActDeleteTweet:
return true
}
}
if user.ID == userId && !isActivation {
switch a {
case ActCreatePrivateTweet,
ActCreatePrivateComment,
ActStickTweet,
ActLockTweet,
ActDeleteTweet:
return true
}
}
if isFriend && isActivation {
switch a {
case ActCreatePublicComment,
ActCreatePublicPicureComment,
ActCreateFriendComment,
ActCreateFriendPicureComment:
return true
}
}
if !isFriend && isActivation {
switch a {
case ActCreatePublicComment,
ActCreatePublicPicureComment:
return true
}
}
return false
}

@ -5,5 +5,5 @@ import (
)
type IndexPostsService interface {
IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error)
IndexPosts(user *model.User, offset int, limit int) ([]*model.PostFormated, error)
}

@ -31,5 +31,5 @@ type TweetSearchService interface {
IndexName() string
AddDocuments(documents DocItems, primaryKey ...string) (bool, error)
DeleteDocuments(identifiers []string) error
Search(q *QueryReq, offset, limit int) (*QueryResp, error)
Search(user *model.User, q *QueryReq, offset, limit int) (*QueryResp, error)
}

@ -0,0 +1,37 @@
package dao
import (
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/model"
)
func newSimpleAuthorizationManageService() *simpleAuthorizationManageService {
return &simpleAuthorizationManageService{
db: conf.DBEngine,
}
}
func (s *simpleAuthorizationManageService) IsAllow(user *model.User, action *core.Action) bool {
// user is activation if had bind phone
isActivation := (len(user.Phone) != 0)
isFriend := s.isFriend(action.UserId)
// TODO: just use defaut act authorization chek rule now
return action.Act.IsAllow(user, action.UserId, isFriend, isActivation)
}
// GetFriendFilter _userId保留未来使用
func (s *simpleAuthorizationManageService) GetFriendFilter(_userId int64) core.FriendFilter {
// TODO: just return an empty friend fileter now
return core.FriendFilter{}
}
func (s *simpleAuthorizationManageService) GetFriendIds(_userId int64) []int64 {
// TODO: just retrun empty now
return nil
}
func (s *simpleAuthorizationManageService) isFriend(_userId int64) bool {
// friend with all world now
return true
}

@ -19,7 +19,7 @@ type postsEntry struct {
posts []*model.PostFormated
}
type indexPostsFunc func(int64, int, int) ([]*model.PostFormated, error)
type indexPostsFunc func(*model.User, int, int) ([]*model.PostFormated, error)
type bigCacheIndexServant struct {
getIndexPosts indexPostsFunc

@ -49,18 +49,18 @@ func newBigCacheIndexServant(getIndexPosts indexPostsFunc) *bigCacheIndexServant
return cacheIndex
}
func (s *bigCacheIndexServant) IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) {
key := s.keyFrom(userId, offset, limit)
func (s *bigCacheIndexServant) IndexPosts(user *model.User, offset int, limit int) ([]*model.PostFormated, error) {
key := s.keyFrom(user, offset, limit)
posts, err := s.getPosts(key)
if err == nil {
logrus.Debugf("get index posts from cache by key: %s userId: %d offset:%d limit:%d", key, userId, offset, limit)
logrus.Debugf("bigCacheIndexServant.IndexPosts get index posts from cache by key: %s", key)
return posts, nil
}
if posts, err = s.getIndexPosts(userId, offset, limit); err != nil {
if posts, err = s.getIndexPosts(user, offset, limit); err != nil {
return nil, err
}
logrus.Debugf("get index posts from database by userId: %d offset:%d limit:%d", userId, offset, limit)
logrus.Debugf("bigCacheIndexServant.IndexPosts get index posts from database by key: %s", key)
s.cachePosts(key, posts)
return posts, nil
}
@ -68,14 +68,14 @@ func (s *bigCacheIndexServant) IndexPosts(userId int64, offset int, limit int) (
func (s *bigCacheIndexServant) getPosts(key string) ([]*model.PostFormated, error) {
data, err := s.cache.Get(key)
if err != nil {
logrus.Debugf("get posts by key: %s from cache err: %v", key, err)
logrus.Debugf("bigCacheIndexServant.getPosts get posts by key: %s from cache err: %v", key, err)
return nil, err
}
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
var posts []*model.PostFormated
if err := dec.Decode(&posts); err != nil {
logrus.Debugf("get posts from cache in decode err: %v", err)
logrus.Debugf("bigCacheIndexServant.getPosts get posts from cache in decode err: %v", err)
return nil, err
}
return posts, nil
@ -85,10 +85,10 @@ func (s *bigCacheIndexServant) cachePosts(key string, posts []*model.PostFormate
entry := &postsEntry{key: key, posts: posts}
select {
case s.cachePostsCh <- entry:
logrus.Debugf("cachePosts by chan of key: %s", key)
logrus.Debugf("bigCacheIndexServant.cachePosts cachePosts by chan of key: %s", key)
default:
go func(ch chan<- *postsEntry, entry *postsEntry) {
logrus.Debugf("cachePosts indexAction by goroutine of key: %s", key)
logrus.Debugf("bigCacheIndexServant.cachePosts cachePosts indexAction by goroutine of key: %s", key)
ch <- entry
}(s.cachePostsCh, entry)
}
@ -98,26 +98,30 @@ func (s *bigCacheIndexServant) setPosts(entry *postsEntry) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(entry.posts); err != nil {
logrus.Debugf("setPosts encode post entry err: %v", err)
logrus.Debugf("bigCacheIndexServant.setPosts setPosts encode post entry err: %v", err)
return
}
if err := s.cache.Set(entry.key, buf.Bytes()); err != nil {
logrus.Debugf("setPosts set cache err: %v", err)
logrus.Debugf("bigCacheIndexServant.setPosts setPosts set cache err: %v", err)
}
logrus.Debugf("setPosts set cache by key: %s", entry.key)
logrus.Debugf("bigCacheIndexServant.setPosts setPosts set cache by key: %s", entry.key)
}
func (s *bigCacheIndexServant) keyFrom(userId int64, offset int, limit int) string {
func (s *bigCacheIndexServant) keyFrom(user *model.User, offset int, limit int) string {
var userId int64 = -1
if user != nil {
userId = user.ID
}
return fmt.Sprintf("index:%d:%d:%d", userId, offset, limit)
}
func (s *bigCacheIndexServant) SendAction(act core.IndexActionT) {
select {
case s.indexActionCh <- act:
logrus.Debugf("send indexAction by chan: %s", act)
logrus.Debugf("bigCacheIndexServant.SendAction send indexAction by chan: %s", act)
default:
go func(ch chan<- core.IndexActionT, act core.IndexActionT) {
logrus.Debugf("send indexAction by goroutine: %s", act)
logrus.Debugf("bigCacheIndexServant.SendAction send indexAction by goroutine: %s", act)
ch <- act
}(s.indexActionCh, act)
}

@ -49,7 +49,7 @@ func newSimpleCacheIndexServant(getIndexPosts indexPostsFunc) *simpleCacheIndexS
return cacheIndex
}
func (s *simpleCacheIndexServant) IndexPosts(_userId int64, offset int, limit int) ([]*model.PostFormated, error) {
func (s *simpleCacheIndexServant) IndexPosts(_user *model.User, offset int, limit int) ([]*model.PostFormated, error) {
posts := s.atomicIndex.Load().([]*model.PostFormated)
end := offset + limit
size := len(posts)
@ -79,7 +79,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, 0, s.maxIndexSize); err == nil {
if s.indexPosts, err = s.getIndexPosts(nil, 0, s.maxIndexSize); err == nil {
s.atomicIndex.Store(s.indexPosts)
} else {
logrus.Errorf("get index posts err: %v", err)

@ -3,22 +3,26 @@ package dao
import (
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/zinc"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
var (
_ core.DataService = (*dataServant)(nil)
_ core.AttachmentCheckService = (*attachmentCheckServant)(nil)
_ core.DataService = (*dataServant)(nil)
_ core.AttachmentCheckService = (*attachmentCheckServant)(nil)
_ core.AuthorizationManageService = (*simpleAuthorizationManageService)(nil)
)
type dataServant struct {
useCacheIndex bool
cacheIndex core.CacheIndexService
useCacheIndex bool
cacheIndex core.CacheIndexService
ams core.AuthorizationManageService
engine *gorm.DB
getIndexPostsFunc indexPostsFunc
}
engine *gorm.DB
zinc *zinc.ZincClient
type simpleAuthorizationManageService struct {
db *gorm.DB
}
type attachmentCheckServant struct {
@ -26,17 +30,18 @@ type attachmentCheckServant struct {
}
func NewDataService() core.DataService {
client := zinc.NewClient(conf.ZincSetting)
ds := &dataServant{
engine: conf.DBEngine,
zinc: client,
ams: NewAuthorizationManageService(),
}
// initialize CacheIndex if needed
ds.useCacheIndex = true
if conf.CfgIf("SimpleCacheIndex") {
ds.cacheIndex = newSimpleCacheIndexServant(ds.getIndexPosts)
ds.getIndexPostsFunc = ds.simpleCacheIndexGetPosts
ds.cacheIndex = newSimpleCacheIndexServant(ds.simpleCacheIndexGetPosts)
} else if conf.CfgIf("BigCacheIndex") {
ds.getIndexPostsFunc = ds.getIndexPosts
ds.cacheIndex = newBigCacheIndexServant(ds.getIndexPosts)
} else {
ds.useCacheIndex = false
@ -49,6 +54,10 @@ func NewDataService() core.DataService {
return ds
}
func NewAuthorizationManageService() core.AuthorizationManageService {
return newSimpleAuthorizationManageService()
}
func NewAttachmentCheckService() core.AttachmentCheckService {
return &attachmentCheckServant{
domain: getOssDomain(),

@ -3,31 +3,62 @@ package dao
import (
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/model"
"github.com/rocboss/paopao-ce/pkg/types"
"github.com/sirupsen/logrus"
)
func (d *dataServant) IndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) {
func (d *dataServant) IndexPosts(user *model.User, offset int, limit int) ([]*model.PostFormated, error) {
if d.useCacheIndex {
if posts, err := d.cacheIndex.IndexPosts(userId, offset, limit); err == nil {
logrus.Debugf("get index posts from cached by userId: %d", userId)
if posts, err := d.cacheIndex.IndexPosts(user, offset, limit); err == nil {
logrus.Debugf("get index posts from cached")
return posts, nil
}
}
logrus.Debugf("get index posts from database but useCacheIndex: %t", d.useCacheIndex)
return d.getIndexPosts(userId, offset, limit)
return d.getIndexPostsFunc(user, offset, limit)
}
// getIndexPosts _userId保留未来使用
// TODO: 未来可能根据userId查询广场推文列表简单做到不同用户的主页都是不同的
func (d *dataServant) getIndexPosts(_userId int64, offset int, limit int) ([]*model.PostFormated, error) {
posts, err := (&model.Post{}).List(d.engine, &model.ConditionsT{
"visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend},
"ORDER": "is_top DESC, latest_replied_on DESC",
}, offset, limit)
// getIndexPosts TODO: 未来可能根据userId查询广场推文列表简单做到不同用户的主页都是不同的
func (d *dataServant) getIndexPosts(user *model.User, offset int, limit int) ([]*model.PostFormated, error) {
predicates := model.Predicates{
"ORDER": types.AnySlice{"is_top DESC, latest_replied_on DESC"},
}
if user == nil {
predicates["visibility = ?"] = types.AnySlice{model.PostVisitPublic}
} else if !user.IsAdmin {
frienddIds := d.ams.GetFriendIds(user.ID)
if len(frienddIds) > 0 {
args := types.AnySlice{model.PostVisitPublic, model.PostVisitPrivate, user.ID, model.PostVisitFriend, frienddIds}
predicates["visibility = ? OR (visibility = ? AND user_id = ?) OR (visibility = ? AND user_id IN ?"] = args
} else {
// TODO: 目前不处理没朋友情况默认用户与世界都是朋友但是目前无从知晓朋友id所以这里特殊处理后续完善
args := types.AnySlice{model.PostVisitPrivate, user.ID, model.PostVisitPublic, model.PostVisitFriend}
predicates["(visibility = ? AND user_id = ?) OR visibility = ? OR visibility = ?"] = args
}
}
posts, err := (&model.Post{}).Fetch(d.engine, predicates, offset, limit)
if err != nil {
logrus.Debugf("dataServant.getIndexPosts err: %v", err)
return nil, err
}
return d.MergePosts(posts)
}
// simpleCacheIndexGetPosts simpleCacheIndex 专属获取广场推文列表函数
func (d *dataServant) simpleCacheIndexGetPosts(_user *model.User, offset int, limit int) ([]*model.PostFormated, error) {
predicates := model.Predicates{
"visibility IN ?": types.AnySlice{[]model.PostVisibleT{model.PostVisitPublic, model.PostVisitPublic}},
"ORDER": types.AnySlice{"is_top DESC, latest_replied_on DESC"},
}
posts, err := (&model.Post{}).Fetch(d.engine, predicates, offset, limit)
if err != nil {
logrus.Debugf("getIndexPosts err: %v", err)
logrus.Debugf("dataServant.simpleCacheIndexGetPosts err: %v", err)
return nil, err
}
return d.MergePosts(posts)
}

@ -24,14 +24,28 @@ type bridgeTweetSearchServant struct {
updateDocsCh chan *documents
}
type tweetSearchFilter struct {
ams core.AuthorizationManageService
}
type zincTweetSearchServant struct {
indexName string
client *zinc.ZincClient
tweetSearchFilter
indexName string
client *zinc.ZincClient
publicFilter string
privateFilter string
friendFilter string
}
type meiliTweetSearchServant struct {
client *meilisearch.Client
index *meilisearch.Index
tweetSearchFilter
client *meilisearch.Client
index *meilisearch.Index
publicFilter string
privateFilter string
friendFilter string
}
func NewTweetSearchService() core.TweetSearchService {

@ -3,6 +3,7 @@ package dao
import (
"github.com/Masterminds/semver/v3"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/model"
"github.com/sirupsen/logrus"
)
@ -33,8 +34,8 @@ func (s *bridgeTweetSearchServant) DeleteDocuments(identifiers []string) error {
return nil
}
func (s *bridgeTweetSearchServant) Search(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
return s.ts.Search(q, offset, limit)
func (s *bridgeTweetSearchServant) Search(user *model.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
return s.ts.Search(user, q, offset, limit)
}
func (s *bridgeTweetSearchServant) updateDocs(doc *documents) {

@ -0,0 +1,46 @@
package dao
import (
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/model"
)
func (s *tweetSearchFilter) filterResp(user *model.User, resp *core.QueryResp) {
// 管理员不过滤
if user != nil && user.IsAdmin {
return
}
var item *model.PostFormated
items := resp.Items
latestIndex := len(items) - 1
if user == nil {
for i := 0; i <= latestIndex; i++ {
item = items[i]
if item.Visibility != model.PostVisitPublic {
items[i] = items[latestIndex]
items = items[:latestIndex]
resp.Total--
latestIndex--
i--
}
}
} else {
var cutFriend, cutPrivate bool
friendFilter := s.ams.GetFriendFilter(user.ID)
for i := 0; i <= latestIndex; i++ {
item = items[i]
cutFriend = (item.Visibility == model.PostVisitFriend && !friendFilter.IsFriend(item.UserID))
cutPrivate = (item.Visibility == model.PostVisitPrivate && user.ID != item.UserID)
if cutFriend || cutPrivate {
items[i] = items[latestIndex]
items = items[:latestIndex]
resp.Total--
latestIndex--
i--
}
}
}
resp.Items = items
}

@ -1,6 +1,8 @@
package dao
import (
"fmt"
"github.com/Masterminds/semver/v3"
"github.com/meilisearch/meilisearch-go"
"github.com/rocboss/paopao-ce/internal/conf"
@ -25,7 +27,7 @@ func newMeiliTweetSearchServant() *meiliTweetSearchServant {
})
searchableAttributes := []string{"content", "tags"}
sortableAttributes := []string{"is_top", "latest_replied_on"}
filterableAttributes := []string{"tags"}
filterableAttributes := []string{"tags", "visibility", "user_id"}
index := client.Index(s.Index)
index.UpdateSearchableAttributes(&searchableAttributes)
@ -34,8 +36,14 @@ func newMeiliTweetSearchServant() *meiliTweetSearchServant {
}
return &meiliTweetSearchServant{
client: client,
index: client.Index(s.Index),
tweetSearchFilter: tweetSearchFilter{
ams: NewAuthorizationManageService(),
},
client: client,
index: client.Index(s.Index),
publicFilter: fmt.Sprintf("visibility=%d", model.PostVisitPublic),
privateFilter: fmt.Sprintf("visibility=%d AND user_id=", model.PostVisitPrivate),
friendFilter: fmt.Sprintf("visibility=%d", model.PostVisitFriend),
}
}
@ -44,7 +52,7 @@ func (s *meiliTweetSearchServant) Name() string {
}
func (s *meiliTweetSearchServant) Version() *semver.Version {
return semver.MustParse("v0.1.0")
return semver.MustParse("v0.2.0")
}
func (s *meiliTweetSearchServant) IndexName() string {
@ -69,54 +77,102 @@ func (s *meiliTweetSearchServant) DeleteDocuments(identifiers []string) error {
return nil
}
func (s *meiliTweetSearchServant) Search(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
func (s *meiliTweetSearchServant) Search(user *model.User, q *core.QueryReq, offset, limit int) (resp *core.QueryResp, err error) {
if q.Type == core.SearchTypeDefault && q.Query != "" {
return s.queryByContent(q, offset, limit)
resp, err = s.queryByContent(user, q, offset, limit)
} else if q.Type == core.SearchTypeTag && q.Query != "" {
return s.queryByTag(q, offset, limit)
resp, err = s.queryByTag(user, q, offset, limit)
} else {
resp, err = s.queryAny(user, offset, limit)
}
if err != nil {
logrus.Errorf("meiliTweetSearchServant.search searchType:%s query:%s error:%v", q.Type, q.Query, err)
return
}
return s.queryAny(offset, limit)
logrus.Debugf("meiliTweetSearchServant.Search type:%s query:%s resp Hits:%d NbHits:%d offset: %d limit:%d ", q.Type, q.Query, len(resp.Items), resp.Total, offset, limit)
s.filterResp(user, resp)
return
}
func (s *meiliTweetSearchServant) queryByContent(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
resp, err := s.index.Search(q.Query, &meilisearch.SearchRequest{
func (s *meiliTweetSearchServant) queryByContent(user *model.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
request := &meilisearch.SearchRequest{
Offset: int64(offset),
Limit: int64(limit),
Sort: []string{"is_top:desc", "latest_replied_on:desc"},
})
}
filter := s.filterList(user)
if len(filter) > 0 {
request.Filter = filter
}
// logrus.Debugf("meiliTweetSearchServant.queryByContent query:%s request%+v", q.Query, request)
resp, err := s.index.Search(q.Query, request)
if err != nil {
return nil, err
}
return s.postsFrom(resp)
}
func (s *meiliTweetSearchServant) queryByTag(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
resp, err := s.index.Search("#"+q.Query, &meilisearch.SearchRequest{
func (s *meiliTweetSearchServant) queryByTag(user *model.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
request := &meilisearch.SearchRequest{
Offset: int64(offset),
Limit: int64(limit),
Filter: []string{"tags." + q.Query + "=1"},
Sort: []string{"is_top:desc", "latest_replied_on:desc"},
})
}
filter := s.filterList(user)
tagFilter := []string{"tags." + q.Query + "=1"}
if len(filter) > 0 {
request.Filter = [][]string{tagFilter, {filter}}
} else {
request.Filter = tagFilter
}
// logrus.Debugf("meiliTweetSearchServant.queryByTag query:%s request%+v", q.Query, request)
resp, err := s.index.Search("#"+q.Query, request)
if err != nil {
return nil, err
}
return s.postsFrom(resp)
}
func (s *meiliTweetSearchServant) queryAny(offset, limit int) (*core.QueryResp, error) {
resp, err := s.index.Search("", &meilisearch.SearchRequest{
func (s *meiliTweetSearchServant) queryAny(user *model.User, offset, limit int) (*core.QueryResp, error) {
request := &meilisearch.SearchRequest{
Offset: int64(offset),
Limit: int64(limit),
Sort: []string{"is_top:desc", "latest_replied_on:desc"},
})
}
filter := s.filterList(user)
if len(filter) > 0 {
request.Filter = filter
}
resp, err := s.index.Search("", request)
if err != nil {
return nil, err
}
return s.postsFrom(resp)
}
func (s *meiliTweetSearchServant) filterList(user *model.User) string {
if user == nil {
return s.publicFilter
}
if user.IsAdmin {
return ""
}
return fmt.Sprintf("%s OR %s OR (%s%d)", s.publicFilter, s.friendFilter, s.privateFilter, user.ID)
}
func (s *meiliTweetSearchServant) postsFrom(resp *meilisearch.SearchResponse) (*core.QueryResp, error) {
logrus.Debugf("resp Hits:%d NbHits:%d offset: %d limit:%d ", len(resp.Hits), resp.NbHits, resp.Offset, resp.Limit)
posts := make([]*model.PostFormated, 0, len(resp.Hits))
for _, hit := range resp.Hits {
item := &model.PostFormated{}

@ -1,6 +1,8 @@
package dao
import (
"fmt"
"github.com/Masterminds/semver/v3"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
@ -12,8 +14,14 @@ import (
func newZincTweetSearchServant() *zincTweetSearchServant {
s := conf.ZincSetting
zts := &zincTweetSearchServant{
indexName: s.Index,
client: zinc.NewClient(s),
tweetSearchFilter: tweetSearchFilter{
ams: NewAuthorizationManageService(),
},
indexName: s.Index,
client: zinc.NewClient(s),
publicFilter: fmt.Sprintf("visibility:%d", model.PostVisitPublic),
privateFilter: fmt.Sprintf("visibility:%d AND user_id:%%d", model.PostVisitPrivate),
friendFilter: fmt.Sprintf("visibility:%d", model.PostVisitFriend),
}
zts.createIndex()
@ -25,7 +33,7 @@ func (s *zincTweetSearchServant) Name() string {
}
func (s *zincTweetSearchServant) Version() *semver.Version {
return semver.MustParse("v0.1.0")
return semver.MustParse("v0.2.0")
}
func (s *zincTweetSearchServant) IndexName() string {
@ -61,16 +69,18 @@ func (s *zincTweetSearchServant) DeleteDocuments(identifiers []string) error {
return nil
}
func (s *zincTweetSearchServant) Search(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
func (s *zincTweetSearchServant) Search(user *model.User, q *core.QueryReq, offset, limit int) (resp *core.QueryResp, err error) {
if q.Type == core.SearchTypeDefault && q.Query != "" {
return s.queryByContent(q, offset, limit)
resp, err = s.queryByContent(user, q, offset, limit)
} else if q.Type == core.SearchTypeTag && q.Query != "" {
return s.queryByTag(q, offset, limit)
resp, err = s.queryByTag(user, q, offset, limit)
}
return s.queryAny(offset, limit)
resp, err = s.queryAny(user, offset, limit)
s.filterResp(user, resp)
return
}
func (s *zincTweetSearchServant) queryByContent(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
func (s *zincTweetSearchServant) queryByContent(user *model.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
resp, err := s.client.EsQuery(s.indexName, map[string]interface{}{
"query": map[string]interface{}{
"match_phrase": map[string]interface{}{
@ -87,7 +97,7 @@ func (s *zincTweetSearchServant) queryByContent(q *core.QueryReq, offset, limit
return s.postsFrom(resp)
}
func (s *zincTweetSearchServant) queryByTag(q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
func (s *zincTweetSearchServant) queryByTag(user *model.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
resp, err := s.client.ApiQuery(s.indexName, map[string]interface{}{
"search_type": "querystring",
"query": map[string]interface{}{
@ -103,7 +113,7 @@ func (s *zincTweetSearchServant) queryByTag(q *core.QueryReq, offset, limit int)
return s.postsFrom(resp)
}
func (s *zincTweetSearchServant) queryAny(offset, limit int) (*core.QueryResp, error) {
func (s *zincTweetSearchServant) queryAny(user *model.User, offset, limit int) (*core.QueryResp, error) {
queryMap := map[string]interface{}{
"query": map[string]interface{}{
"match_all": map[string]string{},

@ -3,6 +3,7 @@ package model
import (
"time"
"github.com/rocboss/paopao-ce/pkg/types"
"gorm.io/gorm"
"gorm.io/plugin/soft_delete"
)
@ -17,6 +18,7 @@ type Model struct {
}
type ConditionsT map[string]interface{}
type Predicates map[string]types.AnySlice
func (m *Model) BeforeCreate(tx *gorm.DB) (err error) {
nowTime := time.Now().Unix()

@ -137,6 +137,30 @@ func (p *Post) List(db *gorm.DB, conditions *ConditionsT, offset, limit int) ([]
return posts, nil
}
func (p *Post) Fetch(db *gorm.DB, predicates Predicates, offset, limit int) ([]*Post, error) {
var posts []*Post
var err error
if offset >= 0 && limit > 0 {
db = db.Offset(offset).Limit(limit)
}
if p.UserID > 0 {
db = db.Where("user_id = ?", p.UserID)
}
for query, args := range predicates {
if query == "ORDER" {
db = db.Order(args[0])
} else {
db = db.Where(query, args...)
}
}
if err = db.Where("is_del = ?", 0).Find(&posts).Error; err != nil {
return nil, err
}
return posts, nil
}
func (p *Post) Count(db *gorm.DB, conditions *ConditionsT) (int64, error) {
var count int64
if p.UserID > 0 {

@ -22,10 +22,10 @@ func GetPostList(c *gin.Context) {
q.Type = "tag"
}
userId, _ := userIdFrom(c)
user, _ := userFrom(c)
offset, limit := app.GetPageOffset(c)
if q.Query == "" && q.Type == "search" {
posts, err := service.GetIndexPosts(userId, offset, limit)
posts, err := service.GetIndexPosts(user, offset, limit)
if err != nil {
logrus.Errorf("service.GetPostList err: %v\n", err)
response.ToErrorResponse(errcode.GetPostsFailed)
@ -38,7 +38,7 @@ func GetPostList(c *gin.Context) {
response.ToResponseList(posts, totalRows)
} else {
posts, totalRows, err := service.GetPostListFromSearch(q, offset, limit)
posts, totalRows, err := service.GetPostListFromSearch(user, q, offset, limit)
if err != nil {
logrus.Errorf("service.GetPostListFromSearch err: %v\n", err)

@ -141,8 +141,8 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos
}
}
// TODO: 目前非私密文章才能有如下操作,后续再优化
if post.Visibility != model.PostVisitPrivate {
// 私密推文不创建标签与用户提醒
if post.Visibility == model.PostVisitPrivate {
// 创建标签
for _, t := range tags {
tag := &model.Tag{
@ -153,6 +153,7 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos
return nil, err
}
}
// 创建用户消息提醒
for _, u := range param.Users {
user, err := ds.GetUserByUsername(u)
@ -170,10 +171,11 @@ func CreatePost(c *gin.Context, userID int64, param PostCreationReq) (*model.Pos
PostID: post.ID,
})
}
// 推送Search
PushPostToSearch(post)
}
// 推送Search
PushPostToSearch(post)
return post, nil
}
@ -240,28 +242,11 @@ func VisiblePost(user *model.User, postId int64, visibility model.PostVisibleT)
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)
PushPostToSearch(post)
} else if visibility == model.PostVisitPrivate {
// 从非私密转为私密需要删除索引
logrus.Debugf("visible post set to private to delete search index: %d, visibility: %s", post.ID, visibility)
DeleteSearchPost(post)
}
return nil
}
@ -413,8 +398,8 @@ func GetPostContentByID(id int64) (*model.PostContent, error) {
return ds.GetPostContentByID(id)
}
func GetIndexPosts(userId int64, offset int, limit int) ([]*model.PostFormated, error) {
return ds.IndexPosts(userId, offset, limit)
func GetIndexPosts(user *model.User, offset int, limit int) ([]*model.PostFormated, error) {
return ds.IndexPosts(user, offset, limit)
}
func GetPostList(req *PostListReq) ([]*model.PostFormated, error) {
@ -431,8 +416,8 @@ func GetPostCount(conditions *model.ConditionsT) (int64, error) {
return ds.GetPostCount(conditions)
}
func GetPostListFromSearch(q *core.QueryReq, offset, limit int) ([]*model.PostFormated, int64, error) {
resp, err := ts.Search(q, offset, limit)
func GetPostListFromSearch(user *model.User, q *core.QueryReq, offset, limit int) ([]*model.PostFormated, int64, error) {
resp, err := ts.Search(user, q, offset, limit)
if err != nil {
return nil, 0, err
}
@ -443,20 +428,15 @@ func GetPostListFromSearch(q *core.QueryReq, offset, limit int) ([]*model.PostFo
return posts, resp.Total, nil
}
func GetPostListFromSearchByQuery(query string, offset, limit int) ([]*model.PostFormated, int64, error) {
func GetPostListFromSearchByQuery(user *model.User, query string, offset, limit int) ([]*model.PostFormated, int64, error) {
q := &core.QueryReq{
Query: query,
Type: "search",
}
return GetPostListFromSearch(q, offset, limit)
return GetPostListFromSearch(user, q, offset, limit)
}
func PushPostToSearch(post *model.Post) {
// TODO: 暂时不索引私密文章,后续再完善
if post.Visibility == model.PostVisitPrivate {
return
}
postFormated := post.Format()
postFormated.User = &model.UserFormated{
ID: post.UserID,
@ -516,11 +496,9 @@ func PushPostsToSearch(c *gin.Context) {
for i := 0; i < nums; i++ {
posts, _ := GetPostList(&PostListReq{
Conditions: &model.ConditionsT{
"visibility IN ?": []model.PostVisibleT{model.PostVisitPublic, model.PostVisitFriend},
},
Offset: i * splitNum,
Limit: splitNum,
Conditions: &model.ConditionsT{},
Offset: i * splitNum,
Limit: splitNum,
})
for _, post := range posts {

@ -0,0 +1,7 @@
package types
type Empty = struct{}
type Any = interface{}
type AnySlice = []interface{}
Loading…
Cancel
Save