@@ -0,0 +1,19 @@
|
||||
package likemod
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type ReqInfo struct {
|
||||
Type string `form:"type" json:"type" binding:"required"` // 点赞类型: SP:长视频 SHORT:短视频 COVER:图文帖子 PIC:图集帖子 SEED_LINK:种子/黄油帖子 TAG:标签 COMMENT:评论 video:动漫 image:漫画 text:小说
|
||||
ObjID primitive.ObjectID `form:"objID" json:"objID" binding:"required"` // 对象id 视频/评论/ACG
|
||||
TagID primitive.ObjectID `form:"tagID" json:"tagID" binding:"omitempty"` // 标签id,在通过标签获取的视频,并对视频点赞时需要传递该标签id
|
||||
}
|
||||
|
||||
type DesLikeReq struct {
|
||||
Type string `form:"type" json:"type" binding:"required"` //点赞类型: SP:长视频 SHORT:短视频 COVER:图文帖子 PIC:图集帖子 SEED_LINK:种子/黄油帖子 TAG:标签 COMMENT:评论 video:动漫 image:漫画 text:小说
|
||||
ObjIDs []primitive.ObjectID `form:"objIDs" json:"objIDs" binding:"required"` //对象id数组
|
||||
}
|
||||
type LikeOIDRes struct {
|
||||
ObjID primitive.ObjectID `bson:"objID"` //点赞对象的ID 如果Type==video 则ObjID为 视频ID, 如果Type==comment ObjID 为评论ID
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package likemod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/db"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/models"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var mdb *db.MongoDB
|
||||
|
||||
const table = models.Like
|
||||
|
||||
func coll(t *db.MongoTool) *db.MongoTool {
|
||||
if t == nil {
|
||||
return mdb.Coll(table)
|
||||
}
|
||||
return t.Coll(table)
|
||||
}
|
||||
|
||||
func initIndex() {
|
||||
many := []mongo.IndexModel{ //batch set indexes //value is the type 1 or -1
|
||||
{
|
||||
Keys: bson.D{{"userID", 1}, {"type", 1}, {"objID", 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{"type", 1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{"objID", 1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{"likedUserID", 1}, {"createdAt", -1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{"createdAt", 1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{"createdAt", -1}},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{"uniq", 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
}
|
||||
_, err := coll(nil).CreateIndex(many)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("%s model set index err ==>[%+v]", table, err))
|
||||
}
|
||||
}
|
||||
|
||||
// ThumbsUp 点赞
|
||||
func InsertOne(l *Like) (err error) {
|
||||
l.Uniq = Unique(l.UserID, l.Type, l.ObjID)
|
||||
l.CreatedAt = time.Now()
|
||||
_, err = coll(nil).InsertOne(l)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "InsertOne", table, "InsertOne", err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// InsertMany 批量点赞
|
||||
func InsertManyBulk(docs []*Like) (insertCount int64, err error) {
|
||||
opts := options.BulkWriteOptions{}
|
||||
opts.SetOrdered(false)
|
||||
models := make([]mongo.WriteModel, 0)
|
||||
for _, v := range docs {
|
||||
models = append(models, mongo.NewInsertOneModel().SetDocument(v))
|
||||
}
|
||||
res, err := coll(nil).Bulk(models, &opts)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "InsertManyBulk", table, "Bulk", err))
|
||||
return
|
||||
}
|
||||
insertCount = res.InsertedCount
|
||||
return
|
||||
}
|
||||
|
||||
// ThumbsDown 取消点赞
|
||||
func DeleteOne(uid uint64, lType string, objID primitive.ObjectID) (err error) {
|
||||
_, err = coll(nil).DeleteOne(bson.M{"userID": uid, "type": lType, "objID": objID})
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "DeleteOne", table, "DeleteOne", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("lType", lType),
|
||||
log.Any("objID", objID),
|
||||
)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ThumbsDownBatch 批量取消点赞
|
||||
func DeleteMany(uid uint64, lType string, objID []primitive.ObjectID) (err error) {
|
||||
_, err = coll(nil).DeleteMany(bson.M{"userID": uid, "type": lType, "objID": bson.M{"$in": objID}})
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "DeleteMany", table, "DeleteOne", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("lType", lType),
|
||||
log.Any("objID", objID),
|
||||
)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindLiked 获取单条点赞记录 验证是否已经点过赞
|
||||
func FindOneByUIDAndObjID(uid uint64, objID primitive.ObjectID) (data Like, err error) {
|
||||
err = coll(nil).FindOne(&data, bson.M{"userID": uid, "objID": objID})
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "FindOneByUIDAndObjID", table, "FindOne", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("objID", objID),
|
||||
)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindLiked 根据对象id获取点赞信息
|
||||
func FindLikesByObjIDS(lType string, uid uint64, CIDs []primitive.ObjectID) (data []Like, err error) {
|
||||
query := bson.M{"userID": uid, "type": lType, "objID": bson.M{"$in": CIDs}}
|
||||
err = coll(nil).Find(&data, query)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "FindLikesByObjIDS", table, "Find", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("lType", lType),
|
||||
log.Any("CIDs", CIDs),
|
||||
)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindLiked 根据对象id获取点赞信息
|
||||
func FindLikesByTypeAndUID(lType string, uid uint64, pageNumber int, pageSize int) (data []LikeOIDRes, hasNext bool, err error) {
|
||||
query := bson.M{"userID": uid, "type": lType}
|
||||
skip := int64(pageSize * (pageNumber - 1))
|
||||
limit := int64(pageSize + 1)
|
||||
opts := options.FindOptions{
|
||||
Skip: &skip,
|
||||
Limit: &limit,
|
||||
Sort: bson.D{{"createdAt", -1}},
|
||||
Projection: bson.M{"_id": 0, "objID": 1},
|
||||
}
|
||||
err = coll(nil).Find(&data, query, &opts)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "FindLikesByTypeAndUID", table, "Find", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("lType", lType),
|
||||
)
|
||||
return
|
||||
}
|
||||
if len(data) > pageSize {
|
||||
hasNext = true
|
||||
data = data[:pageSize]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 从给定的uniq列表中获取like状态映射:uniq->Statue
|
||||
// uniq通过uniq()获取
|
||||
func LikeStatueMap(uniqList []string) (map[string]bool, error) {
|
||||
filter := bson.M{
|
||||
"uniq": bson.M{"$in": uniqList},
|
||||
}
|
||||
existLikeList := make([]Like, 0, len(uniqList))
|
||||
err := coll(nil).Find(&existLikeList, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]bool, len(existLikeList))
|
||||
//初始化
|
||||
for _, uniq := range uniqList {
|
||||
m[uniq] = false
|
||||
}
|
||||
//已经点赞的
|
||||
for _, v := range existLikeList {
|
||||
uniq := v.Uniq
|
||||
m[uniq] = true
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// LLCount 喜欢总数
|
||||
func FindLikeIDSByTypeAndUID(lType string, uid uint64) (like []*SimpleRes, err error) {
|
||||
opts := options.Find()
|
||||
opts.SetProjection(bson.M{"objID": 1})
|
||||
err = coll(nil).Find(&like, bson.M{"userID": uid, "type": lType}, opts)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "FindLikeIDSByTypeAndUID", table, "Find", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("lType", lType),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsLikeVideos 是否点赞批量查询
|
||||
func IsLikeVideos(uid uint64, videoIDs []primitive.ObjectID) (map[primitive.ObjectID]bool, error) {
|
||||
m := make(map[primitive.ObjectID]bool)
|
||||
videoIDs = uniqueVideoIDs(videoIDs)
|
||||
if len(videoIDs) == 0 {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var infos []Like
|
||||
cond := likeVideoStatusFilter(uid, videoIDs)
|
||||
opts := options.Find().SetProjection(bson.M{"_id": 0, "objID": 1})
|
||||
err := coll(nil).Find(&infos, cond, opts)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "IsLikeVideos", table, "Find", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("videoIDs", videoIDs),
|
||||
)
|
||||
return m, err
|
||||
}
|
||||
for _, i := range infos {
|
||||
m[i.ObjID] = true
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func likeVideoStatusFilter(uid uint64, videoIDs []primitive.ObjectID) bson.M {
|
||||
return bson.M{
|
||||
"userID": uid,
|
||||
"type": bson.M{"$in": []string{
|
||||
constant.LikeTypeSP,
|
||||
constant.LikeTypeShort,
|
||||
constant.LikeTypeCover,
|
||||
constant.LikeTypePic,
|
||||
constant.LikeTypeSEED_LINK,
|
||||
constant.LikeTypeAiPlaza,
|
||||
}},
|
||||
"objID": bson.M{"$in": videoIDs},
|
||||
}
|
||||
}
|
||||
|
||||
func uniqueVideoIDs(ids []primitive.ObjectID) []primitive.ObjectID {
|
||||
if len(ids) < 2 {
|
||||
return ids
|
||||
}
|
||||
seen := make(map[primitive.ObjectID]struct{}, len(ids))
|
||||
unique := make([]primitive.ObjectID, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
unique = append(unique, id)
|
||||
}
|
||||
return unique
|
||||
}
|
||||
|
||||
func ListByLikedUserID(uid uint64, skip, limit int64) ([]Like, error) {
|
||||
likedUserID := LikedUserIDMatch{&uid}
|
||||
sort := bson.D{{"createdAt", -1}}
|
||||
return List(sort, skip, limit, likedUserID.New())
|
||||
}
|
||||
|
||||
func GetImageLikeList(uid uint64, skip, limit int64) (out []*Like, err error) {
|
||||
filter := bson.M{"userID": uid, "type": "image"}
|
||||
if err = coll(nil).Find(&out, filter, options.Find().SetSkip(skip).SetLimit(limit).SetSort(bson.M{"createdAt": -1})); err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetImageLikeList", table, "Find", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("skip", skip),
|
||||
log.Any("limit", limit),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsLikeByType 是否点赞批量查询
|
||||
func IsLikeByType(uid uint64, typ []string, IDS []primitive.ObjectID) (map[primitive.ObjectID]bool, error) {
|
||||
m := make(map[primitive.ObjectID]bool)
|
||||
var infos []Like
|
||||
cond := bson.M{"userID": uid, "type": bson.M{"$in": typ}, "objID": bson.M{"$in": IDS}}
|
||||
err := coll(nil).Find(&infos, cond)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "IsLikeByType", table, "Find", err),
|
||||
log.Any("uid", uid),
|
||||
log.Any("IDS", IDS),
|
||||
)
|
||||
return m, err
|
||||
}
|
||||
for _, i := range infos {
|
||||
m[i.ObjID] = true
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package likemod
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"91porn-server/common/constant"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestLikeVideoStatusFilterUsesCompoundIndexFields(t *testing.T) {
|
||||
id1 := primitive.NewObjectID()
|
||||
id2 := primitive.NewObjectID()
|
||||
ids := uniqueVideoIDs([]primitive.ObjectID{id1, id2, id1})
|
||||
|
||||
filter := likeVideoStatusFilter(123, ids)
|
||||
if got := filter["userID"]; got != uint64(123) {
|
||||
t.Fatalf("userID = %#v, want 123", got)
|
||||
}
|
||||
|
||||
typeMatch, ok := filter["type"].(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("type filter = %T, want bson.M", filter["type"])
|
||||
}
|
||||
gotTypes, ok := typeMatch["$in"].([]string)
|
||||
if !ok {
|
||||
t.Fatalf("type.$in = %T, want []string", typeMatch["$in"])
|
||||
}
|
||||
wantTypes := []string{
|
||||
constant.LikeTypeSP,
|
||||
constant.LikeTypeShort,
|
||||
constant.LikeTypeCover,
|
||||
constant.LikeTypePic,
|
||||
constant.LikeTypeSEED_LINK,
|
||||
constant.LikeTypeAiPlaza,
|
||||
}
|
||||
if !reflect.DeepEqual(gotTypes, wantTypes) {
|
||||
t.Fatalf("type.$in = %#v, want %#v", gotTypes, wantTypes)
|
||||
}
|
||||
|
||||
objMatch, ok := filter["objID"].(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("objID filter = %T, want bson.M", filter["objID"])
|
||||
}
|
||||
gotIDs, ok := objMatch["$in"].([]primitive.ObjectID)
|
||||
if !ok {
|
||||
t.Fatalf("objID.$in = %T, want []primitive.ObjectID", objMatch["$in"])
|
||||
}
|
||||
if wantIDs := []primitive.ObjectID{id1, id2}; !reflect.DeepEqual(gotIDs, wantIDs) {
|
||||
t.Fatalf("objID.$in = %#v, want %#v", gotIDs, wantIDs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLikeVideosEmptyIDsReturnsBeforeDatabaseAccess(t *testing.T) {
|
||||
originalDB := mdb
|
||||
mdb = nil
|
||||
t.Cleanup(func() { mdb = originalDB })
|
||||
|
||||
got, err := IsLikeVideos(123, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("IsLikeVideos() error = %v", err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("IsLikeVideos() = %#v, want empty map", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package likemod
|
||||
|
||||
import (
|
||||
"91porn-server/common/pageopt"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Matcher = pageopt.Matcher
|
||||
|
||||
// CreatedAtGTEMatch
|
||||
type CreatedAtGTEMatch struct {
|
||||
GTE *time.Time
|
||||
}
|
||||
|
||||
func (s *CreatedAtGTEMatch) New() Matcher {
|
||||
return pageopt.NewGTEMatch("createdAt", s.GTE)
|
||||
}
|
||||
|
||||
// LikedUserIDMatch
|
||||
type LikedUserIDMatch struct {
|
||||
LikedUserID *uint64
|
||||
}
|
||||
|
||||
func (t *LikedUserIDMatch) New() Matcher {
|
||||
return pageopt.NewAssignMatch("likedUserID", t.LikedUserID)
|
||||
}
|
||||
|
||||
// UserIDMatch
|
||||
type UserIDMatch struct {
|
||||
UserID *uint64
|
||||
}
|
||||
|
||||
func (t *UserIDMatch) New() Matcher {
|
||||
return pageopt.NewAssignMatch("userID", t.UserID)
|
||||
}
|
||||
|
||||
// List
|
||||
func List(sort bson.D, skip, limit int64, matchers ...Matcher) ([]Like, error) {
|
||||
filter := pageopt.MergeM(matchers)
|
||||
opt := &options.FindOptions{}
|
||||
if len(sort) != 0 {
|
||||
opt.SetSort(sort)
|
||||
}
|
||||
opt.SetSkip(skip)
|
||||
opt.SetLimit(limit)
|
||||
list := make([]Like, 0, limit)
|
||||
err := coll(nil).Find(&list, filter, opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Count
|
||||
func Count(matchers ...Matcher) (int64, error) {
|
||||
filter := pageopt.MergeM(matchers)
|
||||
count, err := coll(nil).Count(filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package likemod
|
||||
|
||||
import (
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/db"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
mdb = db.Init(table)
|
||||
initIndex()
|
||||
}
|
||||
|
||||
// Like 点赞
|
||||
type Like struct {
|
||||
ID primitive.ObjectID `bson:"_id,omitempty"` //点赞信息的唯一id
|
||||
Type constant.LikeType `bson:"type"` //点赞对象类型 video:对视频点赞 comment:对评论点赞 image:对图片帖子点赞
|
||||
ObjID primitive.ObjectID `bson:"objID"` //点赞对象的ID 如果Type==video/image 则ObjID为 视频/图片ID, 如果Type==comment ObjID 为评论ID
|
||||
TagID primitive.ObjectID `bson:"tagID,omitempty"` //视频标签列表
|
||||
UserID uint64 `bson:"userID"` //点赞用户ID
|
||||
LikedUserID uint64 `bson:"likedUserID"` //被赞用户ID
|
||||
Uniq string `bson:"uniq"` //uid.objID.type
|
||||
CreatedAt time.Time `bson:"createdAt"` //点赞时间
|
||||
}
|
||||
|
||||
type SimpleRes struct {
|
||||
ObjID primitive.ObjectID `bson:"objID,omitempty"` //点赞信息的唯一id
|
||||
}
|
||||
|
||||
func Unique(uid uint64, typ string, objID primitive.ObjectID) string {
|
||||
list := []string{
|
||||
strconv.FormatInt(int64(uid), 10),
|
||||
objID.Hex(),
|
||||
typ,
|
||||
}
|
||||
s := strings.Join(list, ".")
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user