284 lines
6.9 KiB
Go
284 lines
6.9 KiB
Go
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
|
|
}
|