package vipcardexperimentmod import ( "fmt" "strings" "time" "91porn-server/common/db" "91porn-server/models" "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" ) var mdb *db.MongoDB func Init() { mdb = db.Init(models.VIPCardExperiment) initIndexes() } func experimentColl() *db.MongoTool { return mdb.Coll(models.VIPCardExperiment) } func eventColl() *db.MongoTool { return mdb.Coll(models.VIPCardAnalyticsEvent) } func initIndexes() { if _, err := experimentColl().CreateIndex([]mongo.IndexModel{ { Keys: bson.D{{Key: "experimentId", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{{Key: "activeSlot", Value: 1}}, Options: options.Index(). SetName("uniq_vip_card_active_slot"). SetUnique(true). SetPartialFilterExpression(bson.M{"activeSlot": bson.M{"$gt": ""}}), }, { Keys: bson.D{ {Key: "status", Value: 1}, {Key: "startAt", Value: 1}, {Key: "endAt", Value: 1}, {Key: "publishedAt", Value: -1}, }, }, }); err != nil { panic(fmt.Sprintf("%s model set index err ==>[%+v]", models.VIPCardExperiment, err)) } if _, err := eventColl().CreateIndex([]mongo.IndexModel{ { Keys: bson.D{{Key: "eventId", Value: 1}}, Options: options.Index().SetUnique(true), }, { Keys: bson.D{ {Key: "experimentId", Value: 1}, {Key: "variant", Value: 1}, {Key: "eventName", Value: 1}, {Key: "occurredAt", Value: 1}, }, }, { Keys: bson.D{ {Key: "experimentId", Value: 1}, {Key: "productId", Value: 1}, {Key: "eventName", Value: 1}, }, }, }); err != nil { panic(fmt.Sprintf("%s model set index err ==>[%+v]", models.VIPCardAnalyticsEvent, err)) } } func Publish(experiment *Experiment, operator string) error { experiment.Normalize() if err := experiment.Validate(); err != nil { return err } now := time.Now() experiment.ID = primitive.NewObjectID() experiment.Status = StatusActive experiment.PublishedAt = now experiment.CreatedAt = now experiment.UpdatedAt = now experiment.CreatedBy = operator experiment.DisabledAt = nil experiment.DisabledBy = "" experiment.ActiveSlot = activeSlot return mdb.Trans(func(t *db.MongoTool) error { coll := t.Coll(models.VIPCardExperiment) if _, err := coll.UpdateMany( bson.M{"status": StatusActive}, bson.M{ "$set": bson.M{ "status": StatusDisabled, "disabledAt": now, "disabledBy": operator, "updatedAt": now, }, "$unset": bson.M{"activeSlot": ""}, }, ); err != nil { return err } _, err := coll.InsertOne(experiment) return err }) } func Current(now time.Time) (*Experiment, error) { filter := bson.M{ "status": StatusActive, "$and": bson.A{ bson.M{"$or": bson.A{ bson.M{"startAt": bson.M{"$exists": false}}, bson.M{"startAt": nil}, bson.M{"startAt": bson.M{"$lte": now}}, }}, bson.M{"$or": bson.A{ bson.M{"endAt": bson.M{"$exists": false}}, bson.M{"endAt": nil}, bson.M{"endAt": bson.M{"$gt": now}}, }}, }, } var out Experiment if err := experimentColl().FindOne( &out, filter, options.FindOne().SetSort(bson.D{{Key: "publishedAt", Value: -1}, {Key: "_id", Value: -1}}), ); err != nil { return nil, err } if out.ID.IsZero() { return nil, nil } return &out, nil } func FindByExperimentID(experimentID string) (*Experiment, error) { var out Experiment if err := experimentColl().FindOne(&out, bson.M{"experimentId": experimentID}); err != nil { return nil, err } if out.ID.IsZero() { return nil, nil } return &out, nil } func Disable(experimentID, operator string) (bool, error) { now := time.Now() result, err := experimentColl().UpdateMany( bson.M{"experimentId": experimentID, "status": StatusActive}, bson.M{ "$set": bson.M{ "status": StatusDisabled, "disabledAt": now, "disabledBy": operator, "updatedAt": now, }, "$unset": bson.M{"activeSlot": ""}, }, ) if err != nil { return false, err } return result.ModifiedCount > 0, nil } func InsertEvent(event *AnalyticsEvent) (bool, error) { event.EventID = strings.TrimSpace(event.EventID) event.EventName = strings.ToUpper(strings.TrimSpace(event.EventName)) event.SessionID = strings.TrimSpace(event.SessionID) event.ExperimentID = strings.TrimSpace(event.ExperimentID) event.Variant = strings.ToUpper(strings.TrimSpace(event.Variant)) event.ID = primitive.NewObjectID() event.ReceivedAt = time.Now() result, err := eventColl().UpsertOne( bson.M{"eventId": event.EventID}, bson.M{"$setOnInsert": event}, ) if err != nil { return false, err } return result.UpsertedCount > 0, nil } func EventStatistics(experimentID string) ([]EventStat, []ProductImpressionStat, error) { var eventRaw []struct { ID struct { Variant string `bson:"variant"` EventName string `bson:"eventName"` } `bson:"_id"` People int `bson:"people"` Times int `bson:"times"` } err := eventColl().Aggregate(&eventRaw, []bson.M{ {"$match": bson.M{"experimentId": experimentID}}, {"$group": bson.M{ "_id": bson.M{ "variant": "$variant", "eventName": "$eventName", "uid": "$uid", }, "userTimes": bson.M{"$sum": 1}, }}, {"$group": bson.M{ "_id": bson.M{ "variant": "$_id.variant", "eventName": "$_id.eventName", }, "people": bson.M{"$sum": 1}, "times": bson.M{"$sum": "$userTimes"}, }}, {"$sort": bson.D{{Key: "_id.variant", Value: 1}, {Key: "_id.eventName", Value: 1}}}, }) if err != nil { return nil, nil, err } eventStats := make([]EventStat, 0, len(eventRaw)) for _, item := range eventRaw { eventStats = append(eventStats, EventStat{ Variant: item.ID.Variant, EventName: item.ID.EventName, People: item.People, Times: item.Times, }) } var productRaw []struct { ID struct { Variant string `bson:"variant"` ProductID primitive.ObjectID `bson:"productId"` } `bson:"_id"` People int `bson:"people"` Times int `bson:"times"` } err = eventColl().Aggregate(&productRaw, []bson.M{ {"$match": bson.M{ "experimentId": experimentID, "eventName": EventProductImpression, }}, {"$group": bson.M{ "_id": bson.M{ "variant": "$variant", "productId": "$productId", "uid": "$uid", }, "userTimes": bson.M{"$sum": 1}, }}, {"$group": bson.M{ "_id": bson.M{ "variant": "$_id.variant", "productId": "$_id.productId", }, "people": bson.M{"$sum": 1}, "times": bson.M{"$sum": "$userTimes"}, }}, {"$sort": bson.D{{Key: "_id.variant", Value: 1}, {Key: "times", Value: -1}}}, }) if err != nil { return nil, nil, err } productStats := make([]ProductImpressionStat, 0, len(productRaw)) for _, item := range productRaw { productStats = append(productStats, ProductImpressionStat{ Variant: item.ID.Variant, ProductID: item.ID.ProductID, People: item.People, Times: item.Times, }) } return eventStats, productStats, nil }