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
+38
View File
@@ -0,0 +1,38 @@
package collectmod
import (
"91porn-server/models/v/locmod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// CInfo 查询返回的收藏结构
type CInfo struct {
Video []primitive.ObjectID `bson:"video"`
Tags []primitive.ObjectID `bson:"tags"`
Place []primitive.ObjectID `bson:"place"`
VideoTC int64 `bson:"videoTC"`
TagsTC int64 `bson:"tagsTC"`
PlaceTC int64 `bson:"placeTC"`
}
// ICollectResp 我的收藏回复
type ICollectResp struct {
VInfos []*vidmod.VideoInfo
TInfos []vidmod.TagInfo
LInfos []locmod.Location
VideoTC int64 `bson:"videoTC"`
TagsTC int64 `bson:"tagsTC"`
PlaceTC int64 `bson:"placeTC"`
}
type DoCollectReqInfo struct {
ObjID primitive.ObjectID `form:"objID" json:"objID" binding:"required"` // 收藏的对象的ID
Type string `form:"type" json:"type" binding:"required"` // 收藏类型 SP:长视频 SHORT:短视频 COVER:图文帖子 PIC:图集帖子 SEED_LINK:种子/黄油帖子
IsCollect bool `form:"isCollect" json:"isCollect"` // 收藏or取消收藏
}
type DoBatchCancelCollectReq struct {
ObjIDs []primitive.ObjectID `form:"objIDs" json:"objIDs" binding:"required"` // 取消收藏的ID的合集数组
Type string `form:"type" json:"type" binding:"required"` // 收藏类型 SP:长视频 SHORT:短视频 COVER:图文帖子 PIC:图集帖子 SEED_LINK:种子/黄油帖子
}
+387
View File
@@ -0,0 +1,387 @@
package collectmod
import (
"91porn-server/common/constant"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/models"
"91porn-server/models/commod"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
var mdb *db.MongoDB
var maxCollectTagsSize = 30
const table = models.Collect
func coll(t *db.MongoTool) *db.MongoTool {
if t == nil {
return mdb.Coll(table)
}
return t.Coll(table)
}
// initCollectIndex 初始化索引
func initIndex() {
many := []mongo.IndexModel{
{
Keys: bson.D{{"uid", 1}, {"type", 1}},
},
{
Keys: bson.D{{"uid", 1}, {"type", 1}, {"objID", 1}},
Options: options.Index().SetUnique(true),
},
{
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))
}
return
}
// CollInsertOne 插入收藏消息记录
func CollInsertOne(c *Collect) (err error) {
c.Uniq = Unique(c.UID, c.Type, c.ObjID)
c.CreatedAt = time.Now()
if _, err = coll(nil).InsertOne(c); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollInsertOne", table, "InsertOne", err))
return
}
return
}
// CollInsertMany 插入收藏消息记录
func CollInsertMany(c *[]Collect) (err error) {
if _, err = coll(nil).InsertMany(c); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollInsertMany", table, "InsertMany", err))
return
}
return
}
// CollDeleteOne 取消收藏
func CollDeleteOne(uid uint64, cType string, objID primitive.ObjectID) (err error) {
var query = bson.M{"uid": uid, "type": cType, "objID": objID}
if _, err = coll(nil).DeleteOne(query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollDeleteOne", table, "DeleteOne", err),
log.Any("uid", uid),
log.Any("cType", cType),
log.Any("objID", objID),
)
return
}
return
}
// CollDeleteMany 取消收藏
func CollDeleteMany(ids []string) (err error) {
var query = bson.M{"uniq": bson.M{"$in": ids}}
if _, err = coll(nil).DeleteMany(query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollDeleteMany", table, "DeleteMany", err),
log.Any("ids", ids),
)
return
}
return
}
// CollFindOneByUidAndObjID 获取单条收藏信息 用于验证是否已经收藏
func CollFindOneByUidAndObjID(uid uint64, objID primitive.ObjectID) (data Collect, err error) {
var query = bson.M{"uid": uid, "objID": objID}
if err = coll(nil).FindOne(&data, query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindOneByUidAndObjID", table, "FindOne", err),
log.Any("uid", uid),
log.Any("objID", objID),
)
return
}
return
}
// CollFindOneByUidAndObjIds 获取多条收藏信息 用于验证是否已经收藏
func CollFindOneByUidAndObjIds(uid uint64, objIDS []primitive.ObjectID) (data []Collect, err error) {
var query = bson.M{"uid": uid, "objID": bson.M{"$in": objIDS}}
if err = coll(nil).Find(&data, query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindOneByUidAndObjIds", table, "Find", err),
log.Any("uid", uid),
log.Any("objIDS", objIDS),
)
return
}
return
}
// CollFindCollects 获取收藏列表
func CollFindCollects(uid uint64, cType string, stdQuery commod.StdQuery) (data []Collect, err error) {
*stdQuery.Order = append(*stdQuery.Order, commod.OrderBy{Key: "createdAt", Desc: true})
var query = bson.M{"uid": uid, "type": cType}
if err = coll(nil).Find(&data, query, commod.ConvertToListQuery(stdQuery)); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindOneByUidAndObjID", table, "FindOne", err),
log.Any("uid", uid),
log.Any("objID", cType),
)
return
}
return
}
// CollFindCollectsByUIDAndObjIDs 获取包含指定对象收藏列表
func CollFindCollectsByUIDAndObjIDs(uid uint64, cType string, objIDs []primitive.ObjectID) (data []Collect, err error) {
var query = bson.M{"uid": uid, "type": cType, "objID": bson.M{"$in": objIDs}}
if err = coll(nil).Find(&data, query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindOneByUidAndObjID", table, "FindOne", err),
log.Any("uid", uid),
log.Any("objID", cType),
)
return
}
return
}
// CollFindCollectsByUIDAndObjId 获取包含指定对象收藏列表
func CollFindCollectsByUIDAndObjId(uid uint64, objId primitive.ObjectID) (data Collect, err error) {
var query = bson.M{"uid": uid, "objID": objId}
if err = coll(nil).FindOne(&data, query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindCollectsByUIDAndObjId", table, "FindOne", err),
log.Any("uid", uid),
)
return
}
return
}
func QuickList(uid uint64, cType string, skip, limit int64) ([]Collect, int64, error) {
filter := bson.M{"uid": uid, "type": cType}
const maxLimit = 450 //iphone 11 一屏显示3项 1页3屏 1页9项 50页450项
opt := (&options.FindOptions{}).SetLimit(maxLimit)
var totalList []Collect
err := coll(nil).Find(&totalList, filter, opt) //取总共的数据 最多1000条
if err != nil {
return nil, 0, err
}
total := int64(len(totalList))
if total == 0 {
return []Collect{}, 0, nil
}
//内存中分页 skip + limit ∈ [0, total)
if skip+limit > total {
if skip > total {
return []Collect{}, 0, nil
}
limit = total - skip
}
list := make([]Collect, 0, limit)
for i := skip; i < skip+limit; i++ {
list = append(list, totalList[i])
}
return list, total, err
}
// CollFindCountByUID 获取收藏数量
func CollFindCountByUID(uid uint64) (data int64, err error) {
data, err = coll(nil).Count(bson.M{"uid": uid})
if err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindCountByUID", table, "Count", err), log.Any("uid", uid))
return
}
return
}
// CollFindCountByUidAndType 更具uid/type获取标签数量
func CollFindCountByUidAndType(uid uint64, cType string) (data int64, err error) {
var query = bson.M{"uid": uid, "type": cType, "videoCount": bson.M{"$gt": 0}}
if data, err = coll(nil).Count(query); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindCountByUidAndType", table, "Count", err),
log.Any("uid", uid),
log.Any("cType", cType),
)
return
}
return
}
// CollFindCollectByTypeAndUID 我的收藏
func CollFindCollectByTypeAndUID(uid uint64, cType string) (cIDs []primitive.ObjectID, total int64, err error) {
var opt options.FindOptions
opt.SetSort(bson.M{"_id": -1}).SetLimit(4)
query := bson.M{"uid": uid, "type": cType}
var collects []*Collect
if err = coll(nil).Find(&collects, query, &opt); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindCollectByTypeAndUID", table, "Find", err),
log.Any("uid", uid),
log.Any("cType", cType),
)
return
}
for _, v := range collects {
cIDs = append(cIDs, v.ObjID)
}
total, err = coll(nil).Count(query)
if err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "CollFindCollectByTypeAndUID", table, "Count", err),
log.Any("uid", uid),
log.Any("cType", cType),
)
return
}
return
}
// CollIsExisted 是否收藏
func CollIsExisted(uid uint64, cType string, objID primitive.ObjectID) (isExists bool, err error) {
cnt, err := coll(nil).Count(bson.M{"uid": uid, "type": cType, "objID": objID})
isExists = false
if err != nil || cnt == 0 {
return
}
isExists = true
return
}
// 从给定的uniq列表中获取收藏状态映射:uniq->Statue
// uniq通过uniq()获取
func CltStatueMap(uniqList []string) (map[string]bool, error) {
if len(uniqList) == 0 {
return make(map[string]bool), nil
}
filter := bson.M{
"uniq": bson.M{"$in": uniqList},
}
existCltList := make([]Collect, 0, len(uniqList))
err := coll(nil).Find(&existCltList, filter)
if err != nil {
return nil, err
}
m := make(map[string]bool, len(existCltList))
//初始化
for _, uniq := range uniqList {
m[uniq] = false
}
//已经收藏的
for _, v := range existCltList {
m[v.Uniq] = true
}
return m, nil
}
// IsCollectVideos 是否收藏视频
func IsCollectVideos(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 []Collect
query := collectVideoStatusFilter(uid, videoIDs)
opts := options.Find().SetProjection(bson.M{"_id": 0, "objID": 1})
if err := coll(nil).Find(&infos, query, opts); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "IsCollectVideos", 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 collectVideoStatusFilter(uid uint64, videoIDs []primitive.ObjectID) bson.M {
return bson.M{
"uid": uid,
"type": bson.M{"$in": []string{
constant.CollectTypeSP,
constant.CollectTypeShort,
constant.CollectTypeCover,
constant.CollectTypePIC,
constant.CollectTypeSEED_LINK,
constant.CollectTypeAiPlaza,
}},
"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
}
// GetLocsCollectCnt2Map 获取位置的收藏数
func GetLocsCollectCnt2Map(locIDs []primitive.ObjectID) (map[primitive.ObjectID]int, error) {
if locIDs == nil {
locIDs = []primitive.ObjectID{}
}
m := make(map[primitive.ObjectID]int)
var data []CityCount
p := []bson.M{
{"$match": bson.M{"type": constant.CollectTypeLocation, "objID": bson.M{"$in": locIDs}}},
{"$group": bson.M{"_id": "$objID", "count": bson.M{"$sum": 1}}},
}
if err := coll(nil).Aggregate(&data, p); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetLocsCollectCnt2Map", table, "Aggregate", err), log.Any("locIDs", locIDs))
return nil, err
}
for _, d := range data {
m[d.ID] = d.Count
}
return m, nil
}
// MyCollectTags 获取收藏标签
func MyCollectTags(uid uint64, top int) ([]primitive.ObjectID, error) {
var ids []primitive.ObjectID
stdQuery := commod.StdQuery{Page: &commod.PageBy{Num: 1, Size: uint64(top)}, Order: &[]commod.OrderBy{}}
data, err := CollFindCollects(uid, constant.CollectTypeTag, stdQuery)
if err != nil {
return ids, nil
}
for _, d := range data {
ids = append(ids, d.ObjID)
}
return ids, nil
}
// 根据标签id获取收藏列表
func GetCollectTagList(uid uint64, tagsID []primitive.ObjectID) (data []Collect, err error) {
if tagsID == nil || uid == 0 {
return
}
var query = bson.M{
"uid": uid,
"type": constant.CollectTypeTag,
"objID": bson.M{"$in": tagsID},
}
err = coll(nil).Find(&data, query)
if err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "GetCollectTagList", table, "Find", err),
log.Any("uid", uid),
log.Any("tagsID", tagsID),
)
return
}
return
}
@@ -0,0 +1,68 @@
package collectmod
import (
"reflect"
"testing"
"91porn-server/common/constant"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestCollectVideoStatusFilterUsesCompoundIndexFields(t *testing.T) {
id1 := primitive.NewObjectID()
id2 := primitive.NewObjectID()
ids := uniqueVideoIDs([]primitive.ObjectID{id1, id2, id1})
filter := collectVideoStatusFilter(123, ids)
if got := filter["uid"]; got != uint64(123) {
t.Fatalf("uid = %#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.CollectTypeSP,
constant.CollectTypeShort,
constant.CollectTypeCover,
constant.CollectTypePIC,
constant.CollectTypeSEED_LINK,
constant.CollectTypeAiPlaza,
}
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 TestIsCollectVideosEmptyIDsReturnsBeforeDatabaseAccess(t *testing.T) {
originalDB := mdb
mdb = nil
t.Cleanup(func() { mdb = originalDB })
got, err := IsCollectVideos(123, nil)
if err != nil {
t.Fatalf("IsCollectVideos() error = %v", err)
}
if len(got) != 0 {
t.Fatalf("IsCollectVideos() = %#v, want empty map", got)
}
}
+40
View File
@@ -0,0 +1,40 @@
package collectmod
import (
"91porn-server/common/db"
"go.mongodb.org/mongo-driver/bson/primitive"
"strconv"
"strings"
"time"
)
func Init() {
mdb = db.Init(table)
initIndex()
}
// Collect 用户收藏信息
type Collect struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
ObjID primitive.ObjectID `bson:"objID"` //收藏的对象id
UID uint64 `bson:"uid"` //用户id
Type string `bson:"type"` //收藏类型 video:视频 tag:专题 location:地点 avcomment:AV解说
Uniq string `bson:"uniq"` //uid.objID.type
CreatedAt time.Time `bson:"createdAt"`
}
// CityCount 城市收藏量
type CityCount struct {
ID primitive.ObjectID `bson:"_id"`
Count int `bson:"count"`
}
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
}