Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+19
View File
@@ -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
}
+304
View File
@@ -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)
}
}
+64
View File
@@ -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
}
+42
View File
@@ -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
}